当前位置:网站首页>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 (basic: parameter, return value)
- English topic assignment (26)
- Introduction to RT thread kernel (5) -- memory management
- English topic assignment (27)
- MacBook installation postgresql+postgis
- Stage experience
- A solution to the problem that variables cannot change dynamically when debugging in keil5
- Key review route of probability theory and mathematical statistics examination
- 解密函数计算异步任务能力之「任务的状态及生命周期管理」
猜你喜欢
自动语音识别(ASR)研究综述
The principle of attention mechanism and its application in seq2seq (bahadanau attention)
【科普】热设计基础知识:5G光器件之散热分析
windows下Redis-cluster集群搭建
CUDA Programming atomic operation atomicadd reports error err:msb3721, return code 1
Fuel consumption calculator
Setting up redis cluster cluster under Windows
直播預告 | 容器服務 ACK 彈性預測最佳實踐
计组笔记(1)——校验码、原补码乘除计算、浮点数计算
level18
随机推荐
可观测|时序数据降采样在Prometheus实践复盘
Download the details and sequence of the original data access from the ENA database in EBI
介绍汉明距离及计算示例
概率论与数理统计考试重点复习路线
Power management bus (pmbus)
直播預告 | 容器服務 ACK 彈性預測最佳實踐
TPG x AIDU | AI leading talent recruitment plan in progress!
Network layer - forwarding (IP, ARP, DCHP, ICMP, network layer addressing, network address translation)
A solution to the problem that variables cannot change dynamically when debugging in keil5
Hypothesis testing -- learning notes of Chapter 8 of probability theory and mathematical statistics
A survey of automatic speech recognition (ASR) research
Fonction (sujette aux erreurs)
Neural networks and deep learning Chapter 6: Circular neural networks reading questions
User behavior collection platform
美国5G Open RAN再遭重大挫败,抗衡中国5G技术的图谋已告失败
Neural networks and deep learning Chapter 5: convolutional neural networks reading questions
3 minutes learn to create Google account and email detailed tutorial!
包 类 包的作用域
Private collection project practice sharing [Yugong series] February 2022 U3D full stack class 006 unity toolbar
Leetcode hot topic Hot 100 day 33: "subset"