当子窗口小部件已经滚动到顶部或底部时,Flutter滚动父窗口小部件



当子部件被滚动到顶部或底部时,我正在尝试为用户找到一种滚动父部件的方法,同时保持滚动速度/滚动动量/滚动物理/用户体验。

很好地展示了我正在努力实现的目标(尽管没有BouncingScrollPhysics(:https://i.stack.imgur.com/qpfNP.jpg取自:Flutter:当到达嵌套Listview的底部时,继续在顶部Listview中滚动

我很感激已经有很多类似的问题被问到,其中的答案与我尝试过的NotificationListener有关。然而,这种方法不能保持滚动速度/滚动动量/滚动物理/用户体验,因此导致用户体验质量差。Flutter:当到达嵌套Listview的底部时,继续在顶部Listview中滚动。Listview看起来使用了一种不同的方法,可能会达到所需的结果,但我无法使其正确工作(在允许的操作条件下(。

奇怪的是,目前还没有一个完全满足所需功能的答案,因为它在网站上很常见。很明显,从这个话题的问题数量来看,如果能提供完整的解决方案,我们将不胜感激。

最佳问答;到目前为止:

  • Flutter:ListView:当子ListView到达底部时滚动父ListView-ClampingScrollPhysics在大小容器中不工作
  • 当子列表视图在flutter中结束时,有什么方法可以滚动父列表视图吗
  • Flutter:当到达嵌套Listview的底部时,继续在顶部Listview中滚动

其他类似的问答;如:

  • 如何在子可滚动控件到达flutter 顶部时自动开始滚动父可滚动控件

  • 当到达内部列表的结束/开始时,Flutter嵌套列表滚动父项

我创建了一些基本代码,可以用来测试/演示"每个人"都应该能够轻松理解的解决方案:

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _textAController = ScrollController();
final ScrollController _textBController = ScrollController();
final ScrollController _pageController = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
controller: _pageController,
children: [
Container(
height: 200,
color: Colors.green,
),
Container(
height: 200,
color: Colors.red,
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
padding: const EdgeInsets.only(right: 15),
controller: _textAController,
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const Text(
'Scrollable Child 1',
softWrap: true,
textAlign: TextAlign.center,
),
Container(
color: Colors.amber,
height: 600,
)
],
),
),
),
),
Container(
height: 10,
color: Colors.purple,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
controller: _textBController,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(right: 15),
child: Column(
children: [
const Text(
'Scrollable Child 2',
softWrap: true,
textAlign: TextAlign.center,
),
ListView.separated(
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, index) {
return Container(
height: 12,
width: 50,
color: Colors.grey,
);
},
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 60,
color: Colors.black,
);
},
),
],
),
),
),
),
Container(
height: 100,
color: Colors.blue,
),
],
),
);
}
}

我也遇到了同样的问题,直到我看到你的帖子以及你试图解决同样问题的其他帖子的链接。所以,谢谢你为我指明了正确的方向。关键是使用一些滚动事件提供的速度数据,您可以使用NotificationListener:收听

我的解决方案比我理想中想要的有点棘手,但我相信行为就是你想要的。

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _textAController = ScrollController();
final ScrollController _textBController = ScrollController();
final ScrollController _pageController = ScrollController();
bool _scrolling = false;
getMinMaxPosition(double tryScrollTo){
return 
tryScrollTo < _pageController.position.minScrollExtent 
? _pageController.position.minScrollExtent 
: tryScrollTo > _pageController.position.maxScrollExtent 
? _pageController.position.maxScrollExtent 
: tryScrollTo;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: 
NotificationListener(
onNotification: (ScrollNotification notification) {
//users finger is still on the screen
if(notification is OverscrollNotification && notification.velocity == 0){
var scrollTo = getMinMaxPosition(_pageController.position.pixels + (notification.overscroll));
_pageController.jumpTo(scrollTo);
}
//users finger left screen before limit of the listview was reached, but momentum takes it to the limit and beoyond
else if(notification is OverscrollNotification){
var yVelocity = notification.velocity;
_scrolling = true;//stops other notifiations overriding this scroll animation
var scrollTo = getMinMaxPosition(_pageController.position.pixels + (yVelocity/5));
_pageController.animateTo(scrollTo, duration: const Duration(milliseconds: 1000),curve: Curves.linearToEaseOut).then((value) => _scrolling = false);
}
//users finger left screen after the limit of teh list view was reached
else if(notification is ScrollEndNotification && notification.depth > 0 && !_scrolling){
var yVelocity = notification.dragDetails?.velocity.pixelsPerSecond.dy ?? 0;
var scrollTo = getMinMaxPosition(_pageController.position.pixels - (yVelocity/5));
var scrollToPractical = scrollTo < _pageController.position.minScrollExtent ? _pageController.position.minScrollExtent : scrollTo > _pageController.position.maxScrollExtent ? _pageController.position.maxScrollExtent : scrollTo;
_pageController.animateTo(scrollToPractical, duration: const Duration(milliseconds: 1000),curve: Curves.linearToEaseOut); 
}
return true;
},
child: ListView(
controller: _pageController,
children: [
Container(
height: 200,
color: Colors.green,
),
Container(
height: 200,
color: Colors.red,
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
padding: const EdgeInsets.only(right: 15),
controller: _textAController,
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const Text(
'Scrollable Child 1',
softWrap: true,
textAlign: TextAlign.center,
),
Container(
color: Colors.amber,
height: 600,
)
],
),
),
),
),
Container(
height: 10,
color: Colors.purple,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
controller: _textBController,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(right: 15),
child: Column(
children: [
const Text(
'Scrollable Child 2',
softWrap: true,
textAlign: TextAlign.center,
),
ListView.separated(
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, index) {
return Container(
height: 12,
width: 50,
color: Colors.grey,
);
},
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 60,
color: Colors.black,
);
},
),
],
),
),
),
),
Container(
height: 100,
color: Colors.blue,
),
],
)),
);
}
}

最新更新