当前位置:网站首页>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 :
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 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),
),
),
);
}),
),
],
),
),
));
}
}
边栏推荐
- 包 类 包的作用域
- Function overloading
- Discussion on the dimension of confrontation subspace
- Invalid bound statement (not found) in idea -- problem solving
- Interview related high-frequency algorithm test site 3
- Leetcode hot topic Hot 100 day 33: "subset"
- Reading and visualization of DICOM, MHD and raw files in medical imaging
- PR video clip (project packaging)
- 电源管理总线 (PMBus)
- Variable category (automatic, static, register, external)
猜你喜欢
【虛幻引擎UE】實現UE5像素流部署僅需六步操作少走彎路!(4.26和4.27原理類似)
[phantom engine UE] only six steps are needed to realize the deployment of ue5 pixel stream and avoid detours! (the principles of 4.26 and 4.27 are similar)
49 pictures and 26 questions explain in detail what is WiFi?
Raki's notes on reading paper: code and named entity recognition in stackoverflow
Download the details and sequence of the original data access from the ENA database in EBI
Solution of circular dependency
American 5g open ran suffered another major setback, and its attempt to counter China's 5g technology has failed
【科普】热设计基础知识:5G光器件之散热分析
直播預告 | 容器服務 ACK 彈性預測最佳實踐
Observable time series data downsampling practice in Prometheus
随机推荐
Data security -- 14 -- Analysis of privacy protection governance
How can CIOs use business analysis to build business value?
All in one 1413: determine base
Wenet: E2E speech recognition tool for industrial implementation
MacBook installation postgresql+postgis
How to force activerecord to reload a class- How do I force ActiveRecord to reload a class?
函数(易错)
Discussion on the dimension of confrontation subspace
User behavior collection platform
Leetcode hot topic Hot 100 day 33: "subset"
Aperçu en direct | Services de conteneurs ACK flexible Prediction Best Practices
NetSetMan pro (IP fast switching tool) official Chinese version v5.1.0 | computer IP switching software download
Sword finger offer 07 Rebuild binary tree
托管式服务网络:云原生时代的应用体系架构进化
Hexadecimal to decimal
flutter 对象和列表
Private collection project practice sharing [Yugong series] February 2022 U3D full stack class 006 unity toolbar
Advanced length of redis -- deletion strategy, master-slave replication, sentinel mode
After the deployment of web resources, the navigator cannot obtain the solution of mediadevices instance (navigator.mediadevices is undefined)
Pointer function (basic)