当前位置:网站首页>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 :
VerticalDragGestureRecognizerHandle vertical gesturesHorizontalDragGestureRecognizerHandle 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
NeverScrollableScrollPhysicsIt's forbiddenPageViewandListViewThe rolling effect of - Through the top
RawGestureDetectorOfVerticalDragGestureRecognizerManage gesture events by yourself - To configure
PageControllerandScrollControllerUsed 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
ScrollControllerJudgeListViewWhether or not visible - Judge whether the touch position is
ListIViewWithin the scope of - Judge which one to pass according to the status
ControllerTo produceDragobject , 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
DragUsed to handle subsequent sliding events ,DragThe original event will be processed and then givenScrollPositionTo 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 toListViewrolling - If necessary, pass
_pageControllerSwitch to new_dragObject 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.primaryDeltaJudge 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
ListViewThe location of has not been saved - The product requires removal
ListViewEdge 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 AutomaticKeepAliveClientMixinGive WayListViewKeep 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 :
ListViewIf it has slipped , Will not respond to the topPageViewEvents- If at this time
ListViewNot sliding at the top , Judge whether the gesture position isPageViewin , If it's a responsePageViewEvents
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),
),
),
);
}),
),
],
),
),
));
}
}

边栏推荐
- Error statuslogger log4j2 could not find a logging implementation
- 揭秘技术 Leader 必备的七大清奇脑回路
- Practice | mobile end practice
- You Li takes you to talk about C language 7 (define constants and macros)
- Reading and visualization of DICOM, MHD and raw files in medical imaging
- 蛇形矩阵
- 2022-2028 global and Chinese equipment as a Service Market Research Report
- 程序员应该怎么学数学
- Hexadecimal to decimal
- 机器学习 --- 神经网络
猜你喜欢
![[uniapp] system hot update implementation ideas](/img/1e/77ee9d9f0e08fa2a7734a54e0c5020.png)
[uniapp] system hot update implementation ideas

计组笔记(1)——校验码、原补码乘除计算、浮点数计算

【虚幻引擎UE】实现测绘三脚架展开动画制作

指针函数(基础)

揭秘技术 Leader 必备的七大清奇脑回路

level17

Managed service network: application architecture evolution in the cloud native Era

Network layer - forwarding (IP, ARP, DCHP, ICMP, network layer addressing, network address translation)

TPG x AIDU|AI领军人才招募计划进行中!

防护电路中的元器件
随机推荐
[phantom engine UE] package error appears! Solutions to findpin errors
This is an age of uncertainty
You Li takes you to talk about C language 7 (define constants and macros)
The remainder operation is a hash function
Cookie learning diary 1
[phantom engine UE] realize the animation production of mapping tripod deployment
[AI bulletin 20220211] the hard core up owner has built a lidar and detailed AI accelerator
Private collection project practice sharing [Yugong series] February 2022 U3D full stack class 006 unity toolbar
Chapter 6 text processing tools for shell programming (awk)
托管式服务网络:云原生时代的应用体系架构进化
A survey of automatic speech recognition (ASR) research
History of web page requests
Advanced length of redis -- deletion strategy, master-slave replication, sentinel mode
Burpsuite grabs app packets
机器学习 --- 决策树
蛇形矩阵
[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)
程序员应该怎么学数学
Leetcode 222 number of nodes of complete binary tree
Stage experience