当前位置:网站首页>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),
),
),
);
}),
),
],
),
),
));
}
}

边栏推荐
- 10 programming habits that web developers should develop
- How to get the first few pieces of data of each group gracefully
- 【虚幻引擎UE】实现测绘三脚架展开动画制作
- 首席信息官如何利用业务分析构建业务价值?
- Decimal to hexadecimal
- [phantom engine UE] package error appears! Solutions to findpin errors
- Private collection project practice sharing [Yugong series] February 2022 U3D full stack class 006 unity toolbar
- Raki's notes on reading paper: code and named entity recognition in stackoverflow
- 2022-2028 global and Chinese video coding and transcoding Market Research Report
- Data security -- 14 -- Analysis of privacy protection governance
猜你喜欢

介绍汉明距离及计算示例

直播預告 | 容器服務 ACK 彈性預測最佳實踐

防护电路中的元器件

Wenet: E2E speech recognition tool for industrial implementation

【FineBI】使用FineBI制作自定义地图过程

OWASP top 10 vulnerability Guide (2021)

SQL set operation

Power management bus (pmbus)
![[crampon game] MC tutorial - first day of survival](/img/81/82034c0382f545c39bd8c15f132ec7.jpg)
[crampon game] MC tutorial - first day of survival

Raki's notes on reading paper: soft gazetteers for low resource named entity recognition
随机推荐
Function template
3 minutes learn to create Google account and email detailed tutorial!
All in one 1413: determine base
Hypothesis testing -- learning notes of Chapter 8 of probability theory and mathematical statistics
函数(基本:参数,返回值)
Reading and visualization of DICOM, MHD and raw files in medical imaging
Introduction to RT thread kernel (5) -- memory management
美国5G Open RAN再遭重大挫败,抗衡中国5G技术的图谋已告失败
Key review route of probability theory and mathematical statistics examination
[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)
【虛幻引擎UE】實現UE5像素流部署僅需六步操作少走彎路!(4.26和4.27原理類似)
Wenet: E2E speech recognition tool for industrial implementation
[AI bulletin 20220211] the hard core up owner has built a lidar and detailed AI accelerator
History of web page requests
PHP读取ini文件并修改内容写入
mxnet导入报各种libcudart*.so、 libcuda*.so找不到
Decimal to hexadecimal
Invalid bound statement (not found) in idea -- problem solving
How to remove installed elpa package
49 pictures and 26 questions explain in detail what is WiFi?