当前位置:网站首页>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), ), ), ); }), ), ], ), ), )); }}边栏推荐
- Xcode 6 swift code completion does not work properly - Xcode 6 swift code completion not working
- Report on research and investment prospects of polyglycolic acid industry in China (2022 Edition)
- Awk from entry to earth (15) awk executes external commands
- Internal learning
- Nurse level JDEC addition, deletion, modification and inspection exercise
- Sequence model
- Manjaro install wechat
- Openfeign service interface call
- OpenFeign 服务接口调用
- 没有Kubernetes怎么玩Dapr?
猜你喜欢

微服務入門:Gateway網關

How can we make a monthly income of more than 10000? We media people with low income come and have a look

What should I do if there is a problem with the graphics card screen on the computer

HMS core helps baby bus show high-quality children's digital content to global developers

ArcGIS应用(二十二)Arcmap加载激光雷达las格式数据
![[attack and defense world | WP] cat](/img/01/c5cacfdcca511d4b523611abca6eef.jpg)
[attack and defense world | WP] cat

FOC控制

User login function: simple but difficult

GoLand environment variable configuration

What if I forget the router password
随机推荐
没有Kubernetes怎么玩Dapr?
China electronic grade sulfur trioxide Market Forecast and investment strategy report (2022 Edition)
awk从入门到入土(11)awk getline函数详解
Talk about single case mode
C语言-入门-基础-语法-数据类型(四)
[attack and defense world | WP] cat
Implementation principle of redis string and sorted set
Solve the problem of "Chinese garbled MySQL fields"
C language - Introduction - Foundation - syntax - [main function, header file] (II)
FOC control
System disk expansion in virtual machine
std::is_ union,std::is_ class,std::integral_ constant
MySQL relearn 1-centos install mysql5.7
09 softmax regression + loss function
根据数字显示中文汉字
NewH3C——ACL
Démarrage des microservices: passerelle
What if I forget the router password
Openfeign service interface call
Report on research and investment prospects of polyglycolic acid industry in China (2022 Edition)