当前位置:网站首页>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 :
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 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), ), ), ); }), ), ], ), ), )); }}
边栏推荐
- Leetcode topic [array] -136- numbers that appear only once
- Sequence model
- ctfshow web255 web 256 web257
- 上周热点回顾(6.27-7.3)
- Awk from entry to soil (5) simple condition matching
- Research and investment strategy report of China's electronic hydrogen peroxide industry (2022 Edition)
- Jianzhi offer 09 realizes queue with two stacks
- [Chongqing Guangdong education] National Open University spring 2019 455 logistics practice reference questions
- Implementation principle of redis string and sorted set
- China electronic grade sulfur trioxide Market Forecast and investment strategy report (2022 Edition)
猜你喜欢
Explain TCP protocol in detail three handshakes and four waves
How can we make a monthly income of more than 10000? We media people with low income come and have a look
【无标题】转发最小二乘法
System disk expansion in virtual machine
Awk from entry to earth (12) awk can also write scripts to replace the shell
C语言-入门-基础-语法-[主函数,头文件](二)
What exactly is DAAS data as a service? Don't be misled by other DAAS concepts
How to solve the problem of computer jam and slow down
Bishi blog (13) -- oral arithmetic test app
Leetcode topic [array] -136- numbers that appear only once
随机推荐
随机事件的关系与运算
LinkedList in the list set is stored in order
awk从入门到入土(5)简单条件匹配
Awk from entry to earth (18) GAW K line manual
Flutter 集成 amap_flutter_location
《网络是怎么样连接的》读书笔记 - 认识网络基础概念(一)
微服務入門:Gateway網關
09 softmax regression + loss function
Codeforces Global Round 21(A-E)
4 small ways to make your Tiktok video clearer
Basic operations of databases and tables ----- view data tables
awk从入门到入土(11)awk getline函数详解
awk从入门到入土(14)awk输出重定向
【无标题】转发最小二乘法
From scratch, use Jenkins to build and publish pipeline pipeline project
《网络是怎么样连接的》读书笔记 - 集线器、路由器和路由器(三)
没有Kubernetes怎么玩Dapr?
C language - Introduction - Foundation - syntax - [operators, type conversion] (6)
Codeforces Round #750 (Div. 2)(A,B,C,D,F1)
20220701 Barbalat引理证明