当前位置:网站首页>Flutter tips: various fancy nesting of listview and pageview
Flutter tips: various fancy nesting of listview and pageview
2022-07-04 08:58:00 【GSYTech】
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 listViewclass 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), ), ), ); }), ), ], ), ), )); }}边栏推荐
- Horizon sunrise X3 PI (I) first boot details
- Awk from getting started to digging in (9) circular statement
- The basic syntax of mermaid in typera
- Target detection -- intensive reading of yolov3 paper
- Use Alibaba cloud NPM image acceleration
- awk从入门到入土(9)循环语句
- Codeforces Round #793 (Div. 2)(A-D)
- [untitled] forwarding least square method
- Awk from entry to soil (5) simple condition matching
- AI Winter Olympics | is the future coming? Enter the entrance of the meta universe - virtual digital human
猜你喜欢

地平线 旭日X3 PI (一)首次开机细节

Educational Codeforces Round 115 (Rated for Div. 2)

Horizon sunrise X3 PI (I) first boot details
![[attack and defense world | WP] cat](/img/01/c5cacfdcca511d4b523611abca6eef.jpg)
[attack and defense world | WP] cat
](/img/3f/4d8f4c77d9fde5dd3f53ef890ecfa8.png)
C語言-入門-基礎-語法-[運算符,類型轉換](六)

4 small ways to make your Tiktok video clearer

How to re enable local connection when the network of laptop is disabled

Dede plug-in (multi-function integration)

ArcGIS application (XXII) ArcMap loading lidar Las format data

Nurse level JDEC addition, deletion, modification and inspection exercise
随机推荐
Codeforces Round #803 (Div. 2)(A-D)
Awk from digging into the ground to getting started (10) awk built-in functions
AI Winter Olympics | is the future coming? Enter the entrance of the meta universe - virtual digital human
微服務入門:Gateway網關
20220701 Barbalat引理证明
Awk from getting started to digging in (9) circular statement
What if I forget the router password
The map set type is stored in the form of key value pairs, and the iterative traversal is faster than the list set
awk从入门到入土(8)数组
LinkedList in the list set is stored in order
Newh3c - network address translation (NAT)
awk从入门到入土(18)gawk线上手册
awk从入门到入土(7)条件语句
Getting started with microservices: gateway gateway
如何通过antd的upload控件,将图片以文件流的形式发送给服务器
C#实现一个万物皆可排序的队列
Implementation principle of redis string and sorted set
awk从入门到入土(11)awk getline函数详解
Comparison between sentinel and hystrix
Explain TCP protocol in detail three handshakes and four waves