当前位置:网站首页>Flutter tips: various fancy nesting of listview and pageview
Flutter tips: various fancy nesting of listview and pageview
2022-07-05 04:37:00 【Love cat de Xiaoguo】
This time Flutter The trick is ListView
and PageView
Fancy nesting , Different Scrollable
I believe you will not be unfamiliar with the nested conflict problem of , Passed today ListView
and PageView
The three nesting modes of take you to gain some different tips .
Normal nesting
The most common nesting should be horizontal PageView
Plus longitudinal ListView
The combination of , In general, there will be no problem with this combination , Unless you insist on sliding sideways .
Recently, I happened to meet several people asking at the same time :“ Oblique slip ListView
Easy to switch to PageView
slide ” The problem of , as follows GIF Shown , When the user is sliding ListView
when , After the sliding angle belt is tilted , What may cause sliding is PageView
instead of ListView
.
Although I don't think this is a problem from my personal experience , But if the product insists on your modification , Do you want to rewrite it yourself PageView
Are your gestures responsive ?
Let's look at it briefly , Whether it's PageView
still ListView
Their sliding effect comes from Scrollable
, and Scrollable
Internal response to different directions , It's through RawGestureDetector
complete :
VerticalDragGestureRecognizer
Handle vertical gesturesHorizontalDragGestureRecognizer
Handle horizontal gestures
So simply look at the judgment logic of their response , You can see a very interesting method computeHitSlop
: according to pointer The type of determines, of course, the smallest pixel needed to hit , Touch the default is kTouchSlop (18.0).
Do you have a flash of inspiration when you see this : If we put PageView
Of touchSlop Revised , Is it possible to adjust the sensitivity of its response ? It happens to be computeHitSlop
In the method , It can go through DeviceGestureSettings
To configure the , and DeviceGestureSettings
From MediaQuery
, So the following code shows :
body: MediaQuery(
/// Raise height touchSlop To 50 , such pageview Sliding may have a little effect ,
/// But the problem of oblique sliding trigger is probably handled
data: MediaQuery.of(context).copyWith(
gestureSettings: DeviceGestureSettings(
touchSlop: 50,
)),
child: PageView(
scrollDirection: Axis.horizontal,
pageSnapping: true,
children: [
HandlerListView(),
HandlerListView(),
],
),
),
Tip 1 : By nesting a MediaQuery
, Then adjust gestureSettings
Of touchSlop
To modify PageView
The lightness of , And don't forget , You need to put ListView
Of touchSlop
Switching will default Of kTouchSlop
:
class HandlerListView extends StatefulWidget {
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<HandlerListView> {
@override
Widget build(BuildContext context) {
return MediaQuery(
/// here touchSlop You need to return to the default
data: MediaQuery.of(context).copyWith(
gestureSettings: DeviceGestureSettings(
touchSlop: kTouchSlop,
)),
child: ListView.separated(
itemCount: 15,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
separatorBuilder: (context, index) {
return const Divider(
thickness: 3,
);
},
),
);
}
}
Finally, let's see the effect , as follows GIF Shown , Now even if you slide sideways , It also triggers PageView
Horizontal sliding of , Triggered only when moving laterally PageView
The gesture , Of course , If there is any problem with this rough writing , It's probably reduced PageView
Sensitivity of response .
In the same direction PageView nesting ListView
After introducing the general use , Then something different , Switch vertically PageView
Nested vertically scrolling ListView
, Is your first feeling unreliable , Why is there such a scene ?
For products , They won't think about how you achieve it , They just slap their heads and say Taobao can , Why can't you , So if it's you , What would you do ?
And about this demand , The result of the current discussion in the community is : hold PageView
and ListView
Sliding of is disabled , And then through RawGestureDetector
Manage yourself .
If you are not interested in implementing logical analysis , You can see directly at the end of this section Source link .
Don't panic when you see your own management , Although we should realize it by ourselves PageView
and ListView
Gesture distribution , But there is no need to rewrite PageView
and ListView
, We can reuse their Darg
Response logic , As shown in the following code :
- adopt
NeverScrollableScrollPhysics
It's forbiddenPageView
andListView
The rolling effect of - Through the top
RawGestureDetector
OfVerticalDragGestureRecognizer
Manage gesture events by yourself - To configure
PageController
andScrollController
Used to get status
body: RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer instance) {
instance
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
})
},
behavior: HitTestBehavior.opaque,
child: PageView(
controller: _pageController,
scrollDirection: Axis.vertical,
/// Mask the default sliding response
physics: const NeverScrollableScrollPhysics(),
children: [
ListView.builder(
controller: _listScrollController,
/// Mask the default sliding response
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(title: Text('List Item $index'));
},
itemCount: 30,
),
Container(
color: Colors.green,
child: Center(
child: Text(
'Page View',
style: TextStyle(fontSize: 50),
),
),
)
],
),
),
And then let's see _handleDragStart
Realization , As shown in the following code , Making gestures details
when , We mainly judge :
- adopt
ScrollController
JudgeListView
Whether or not visible - Judge whether the touch position is
ListIView
Within the scope of - Judge which one to pass according to the status
Controller
To produceDrag
object , Used to respond to subsequent sliding events
void _handleDragStart(DragStartDetails details) {
/// First judge Listview Whether it is visible or can be called
/// Generally invisible hasClients false , because PageView either keepAlive
if (_listScrollController?.hasClients == true &&
_listScrollController?.position.context.storageContext != null) {
/// obtain ListView Of renderBox
final RenderBox? renderBox = _listScrollController
?.position.context.storageContext
.findRenderObject() as RenderBox;
/// Judge whether the touch position is ListView Inside
/// Generally, it is not within the scope because ListView It has slid up , The coordinate position is inconsistent with the touch position
if (renderBox?.paintBounds
.shift(renderBox.localToGlobal(Offset.zero))
.contains(details.globalPosition) ==
true) {
_activeScrollController = _listScrollController;
_drag = _activeScrollController?.position.drag(details, _disposeDrag);
return;
}
}
/// At this time, it can be regarded as PageView Need to slide
_activeScrollController = _pageController;
_drag = _pageController?.position.drag(details, _disposeDrag);
}
We are mainly at the beginning of touch , When judging the object that needs response ListView
still PageView
, And then through _activeScrollController
Save the response object of course , And through Controller Generate Drag
object .
In short : When the sliding event occurs , By default, a
Drag
Used to handle subsequent sliding events ,Drag
The original event will be processed and then givenScrollPosition
To trigger the subsequent sliding effect .
And then _handleDragUpdate
In the method , It mainly determines whether the response needs to be switched to PageView
:
- If you don't need it, continue to use what you got before
_drag?.update(details)
Respond toListView
rolling - If necessary, pass
_pageController
Switch to new_drag
Object is used to respond to
void _handleDragUpdate(DragUpdateDetails details) {
if (_activeScrollController == _listScrollController &&
/// Move your fingers up , That is, it is about to show the bottom PageView
details.primaryDelta! < 0 &&
/// At the bottom , Switch to PageView
_activeScrollController?.position.pixels ==
_activeScrollController?.position.maxScrollExtent) {
/// Switch the corresponding controller
_activeScrollController = _pageController;
_drag?.cancel();
/// Reference resources Scrollable in
/// Because it is switching controller , That is to update Drag
/// Drag and drop the process to switch to PageView in , So we need to DragStartDetails
/// So we need to DragUpdateDetails become DragStartDetails
/// Extract PageView Inside Drag The corresponding details
_drag = _pageController?.position.drag(
DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition),
_disposeDrag);
}
_drag?.update(details);
}
Here is a little knowledge : As shown in the above code , We can simply go through
details.primaryDelta
Judge whether the sliding direction and moving is the spindle
Finally, as follows GIF Shown , You can see PageView
nesting ListView
Sliding in the same direction can work normally , But there are still two small problems , As can be seen from the illustration :
- After switching
ListView
The location of has not been saved - The product requires removal
ListView
Edge overflow effect
So we need to deal with ListView
Make one KeepAlive , Then use a simple method to remove Android Edge sliding Material effect :
- adopt
with AutomaticKeepAliveClientMixin
Give WayListView
Keep the sliding position after switching - adopt
ScrollConfiguration.of(context).copyWith(overscroll: false)
Rapid removal Scrollable The edge of Material effect
child: PageView(
controller: _pageController,
scrollDirection: Axis.vertical,
/// Get rid of Android On the default edge drag effect
scrollBehavior:
ScrollConfiguration.of(context).copyWith(overscroll: false),
/// Yes PageView Inside ListView do KeepAlive Remember the location
class KeepAliveListView extends StatefulWidget {
final ScrollController? listScrollController;
final int itemCount;
KeepAliveListView({
required this.listScrollController,
required this.itemCount,
});
@override
KeepAliveListViewState createState() => KeepAliveListViewState();
}
class KeepAliveListViewState extends State<KeepAliveListView>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return ListView.builder(
controller: widget.listScrollController,
/// Mask the default sliding response
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(title: Text('List Item $index'));
},
itemCount: widget.itemCount,
);
}
@override
bool get wantKeepAlive => true;
}
So here we have another trick to unlock : adopt ScrollConfiguration.of(context).copyWith(overscroll: false)
Rapid removal Android Sliding to the edge Material 2 effect , Why do you say Material2, because Material3 Change up , concrete : Flutter 3 Under the ThemeExtensions and Material3 .
The source code of this section can be seen : https://github.com/CarGuo/gsy_flutter_demo/blob/7838971cefbf19bb53a71041cd100c4c15eb6443/lib/widget/vp_list_demo_page.dart#L75
In the same direction ListView nesting PageView
Is there anything more unconventional ? The answer is yes , After all, the small head of the product , How can I not think of Sliding vertically ListView
Nested vertically switched PageView
This demand .
With the front idea , In fact, the realization of this logic is similar : hold PageView
and ListView
Sliding of is disabled , And then through RawGestureDetector
Manage yourself , The difference is the difference in gesture distribution .
RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer instance) {
instance
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
})
},
behavior: HitTestBehavior.opaque,
child: ListView.builder(
/// Mask the default sliding response
physics: NeverScrollableScrollPhysics(),
controller: _listScrollController,
itemCount: 5,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
height: 300,
child: KeepAlivePageView(
pageController: _pageController,
itemCount: itemCount,
),
);
}
return Container(
height: 300,
color: Colors.greenAccent,
child: Center(
child: Text(
"Item $index",
style: TextStyle(fontSize: 40, color: Colors.blue),
),
));
}),
)
It's also in _handleDragStart
In the method , First of all, we need to judge :
ListView
If it has slipped , Will not respond to the topPageView
Events- If at this time
ListView
Not sliding at the top , Judge whether the gesture position isPageView
in , If it's a responsePageView
Events
void _handleDragStart(DragStartDetails details) {
/// As long as it's not the top , No response PageView The slide of
/// So this judgment only supports vertical PageView stay ListView At the top of the
if (_listScrollController.offset > 0) {
_activeScrollController = _listScrollController;
_drag = _listScrollController.position.drag(details, _disposeDrag);
return;
}
/// At this time ListView At the top of the
if (_pageController.hasClients) {
/// obtain PageView
final RenderBox renderBox =
_pageController.position.context.storageContext.findRenderObject()
as RenderBox;
/// Judge whether the touch range is PageView
final isDragPageView = renderBox.paintBounds
.shift(renderBox.localToGlobal(Offset.zero))
.contains(details.globalPosition);
/// If in PageView Switch to PageView
if (isDragPageView) {
_activeScrollController = _pageController;
_drag = _activeScrollController.position.drag(details, _disposeDrag);
return;
}
}
/// be not in PageView I will continue to respond ListView
_activeScrollController = _listScrollController;
_drag = _listScrollController.position.drag(details, _disposeDrag);
}
And then _handleDragUpdate
In the method , Determine if the PageView
Has slid to the last page , Also switch the sliding event to ListView
void _handleDragUpdate(DragUpdateDetails details) {
var scrollDirection = _activeScrollController.position.userScrollDirection;
/// Judge if the response at this time is still _pageController, Is it the last page
if (_activeScrollController == _pageController &&
scrollDirection == ScrollDirection.reverse &&
/// Is it the last page , Switch back to... On the last page pageController
(_pageController.page != null &&
_pageController.page! >= (itemCount - 1))) {
/// Switch back to the ListView
_activeScrollController = _listScrollController;
_drag?.cancel();
_drag = _listScrollController.position.drag(
DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition),
_disposeDrag);
}
_drag?.update(details);
}
Of course , As well as KeepAlive And remove the list Material Edge effect , The results are as follows GIF Shown .
The source code of this section can be seen :https://github.com/CarGuo/gsy_flutter_demo/blob/7838971cefbf19bb53a71041cd100c4c15eb6443/lib/widget/vp_list_demo_page.dart#L262
Finally, add a little trick : If you need Flutter Print the process of gesture competition , You can configure the debugPrintGestureArenaDiagnostics = true;
To make the Flutter The processing process of outputting gesture competition .
import 'package:flutter/gestures.dart';
void main() {
debugPrintGestureArenaDiagnostics = true;
runApp(MyApp());
}
Last
So to conclude , This chapter introduces how to pass Darg
Solve various gesture conflicts caused by nesting , I believe you also know how to use Controller
and Darg
To quickly customize some sliding requirements , for example ListView
linkage ListView
Differential sliding effect :
///listView linkage listView
class ListViewLinkListView extends StatefulWidget {
@override
_ListViewLinkListViewState createState() => _ListViewLinkListViewState();
}
class _ListViewLinkListViewState extends State<ListViewLinkListView> {
ScrollController _primaryScrollController = ScrollController();
ScrollController _subScrollController = ScrollController();
Drag? _primaryDrag;
Drag? _subDrag;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_primaryScrollController.dispose();
_subScrollController.dispose();
super.dispose();
}
void _handleDragStart(DragStartDetails details) {
_primaryDrag =
_primaryScrollController.position.drag(details, _disposePrimaryDrag);
_subDrag = _subScrollController.position.drag(details, _disposeSubDrag);
}
void _handleDragUpdate(DragUpdateDetails details) {
_primaryDrag?.update(details);
/// Divide 10 Achieve the difference effect
_subDrag?.update(DragUpdateDetails(
sourceTimeStamp: details.sourceTimeStamp,
delta: details.delta / 30,
primaryDelta: (details.primaryDelta ?? 0) / 30,
globalPosition: details.globalPosition,
localPosition: details.localPosition));
}
void _handleDragEnd(DragEndDetails details) {
_primaryDrag?.end(details);
_subDrag?.end(details);
}
void _handleDragCancel() {
_primaryDrag?.cancel();
_subDrag?.cancel();
}
void _disposePrimaryDrag() {
_primaryDrag = null;
}
void _disposeSubDrag() {
_subDrag = null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ListViewLinkListView"),
),
body: RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer instance) {
instance
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
})
},
behavior: HitTestBehavior.opaque,
child: ScrollConfiguration(
/// Get rid of Android On the default edge drag effect
behavior:
ScrollConfiguration.of(context).copyWith(overscroll: false),
child: Row(
children: [
new Expanded(
child: ListView.builder(
/// Mask the default sliding response
physics: NeverScrollableScrollPhysics(),
controller: _primaryScrollController,
itemCount: 55,
itemBuilder: (context, index) {
return Container(
height: 300,
color: Colors.greenAccent,
child: Center(
child: Text(
"Item $index",
style: TextStyle(
fontSize: 40, color: Colors.blue),
),
));
})),
new SizedBox(
width: 5,
),
new Expanded(
child: ListView.builder(
/// Mask the default sliding response
physics: NeverScrollableScrollPhysics(),
controller: _subScrollController,
itemCount: 55,
itemBuilder: (context, index) {
return Container(
height: 300,
color: Colors.deepOrange,
child: Center(
child: Text(
"Item $index",
style:
TextStyle(fontSize: 40, color: Colors.white),
),
),
);
}),
),
],
),
),
));
}
}
边栏推荐
- 2022-2028 global and Chinese virtual data storage Market Research Report
- CUDA Programming atomic operation atomicadd reports error err:msb3721, return code 1
- 解密函数计算异步任务能力之「任务的状态及生命周期管理」
- [crampon game] MC tutorial - first day of survival
- Network security - record web vulnerability fixes
- Function (basic: parameter, return value)
- [crampon programming] lintcode decoding Encyclopedia - 1100 strange printer
- About the prompt loading after appscan is opened: guilogic, it keeps loading and gets stuck. My personal solution. (it may be the first solution available in the whole network at present)
- Construction d'un Cluster redis sous Windows
- level18
猜你喜欢
函數(易錯)
Raki's notes on reading paper: code and named entity recognition in stackoverflow
C26451: arithmetic overflow: use the operator * on a 4-byte value, and then convert the result to an 8-byte value. To avoid overflow, cast the value to wide type before calling the operator * (io.2)
Fonction (sujette aux erreurs)
Network security - record web vulnerability fixes
假设检验——《概率论与数理统计》第八章学习笔记
User behavior collection platform
【科普】热设计基础知识:5G光器件之散热分析
防护电路中的元器件
3 minutes learn to create Google account and email detailed tutorial!
随机推荐
Function overloading
美国5G Open RAN再遭重大挫败,抗衡中国5G技术的图谋已告失败
CSDN正文自动生成目录
Raki's notes on reading paper: soft gazetteers for low resource named entity recognition
Network security - record web vulnerability fixes
Official announcement! The third cloud native programming challenge is officially launched!
Hexadecimal to decimal
Decimal to hexadecimal
Download the details and sequence of the original data access from the ENA database in EBI
蛇形矩阵
Power management bus (pmbus)
[popular science] basic knowledge of thermal design: heat dissipation analysis of 5g optical devices
Debug insights
【虚幻引擎UE】运行和启动的区别,常见问题分析
可观测|时序数据降采样在Prometheus实践复盘
如何优雅的获取每个分组的前几条数据
[moteur illusoire UE] il ne faut que six étapes pour réaliser le déploiement du flux de pixels ue5 et éviter les détours! (4.26 et 4.27 principes similaires)
2022-2028 global and Chinese video coding and transcoding Market Research Report
level18
自动语音识别(ASR)研究综述