Flutter的上滑面板插件中的ScrollController如何操作?
How to manipulate the ScrollController in Flutter's sliding up panel plugin?
我正在使用 Flutter 的 sliding_up_panel 插件。
我想在从我的应用程序抽屉中选择新项目时将面板滚动到顶部。目前选择一个新项目会关闭面板并刷新面板内容。然后将其打开到 200px 峰值,但不会将面板的滚动位置重置为顶部。
我一直在兜圈子,以略有不同的方式尝试相同的解决方案,但一无所获。
我尝试过的:
我有全球
PanelController slidingPanelController = new PanelController();
ScrollController slideUpPanelScrollController = new ScrollController();
我尝试将我的全局 slideUpPanelScrollController
附加到面板的列表视图,但是当向上滑动面板的 ListView 时,它同时开始关闭整个面板。如果您向上滚动以阅读您浏览过的内容,那么,您将无法阅读,因为它正在消失。
防止此错误很容易,您可以按照插件示例中的规范方式进行操作,从 SlidingPanel
传递 ScrollController,然后在面板的 Listview 中创建一个本地 ScrollController。
panelBuilder: (slideUpPanelScrollController) => _scrollingList(presentLocation, slideUpPanelScrollController)
问题是,您无法在新的应用程序抽屉选择上滚动面板,因为控制器现在是本地的。
我尝试在本地列表视图 ScrollController 上放置一个侦听器,_slideUpPanelScrollController
并测试 panelController.close():
if(slidingPanelController.isPanelClosed) _slideUpPanelScrollController.jumpTo(0);
但是侦听器阻止了面板滑动,触发了滑动事件但面板没有滑动,或者也非常不情愿。
在 ListView 中途显示内容的面板中打开新选择的内容是一种故障用户体验。我会喜欢一些想法或更好的解决方案。
我需要它,所以当面板关闭时,我可以
slideUpPanelScrollController.jumpTo(0);
我需要将全局控制器附加到面板 ListView 的本地控制器,或者我需要一种方法来访问本地控制器以从我的 _scrollingList()
函数中触发它的 Scroll。
这是面板小部件:
SlidingUpPanel(
key: Key("slidingUpPanelKey"),
borderRadius: slidingPanelBorderRadius,
parallaxEnabled: false,
controller: slidingPanelController,
isDraggable: isDraggableBool,
onPanelOpened: () {
},
onPanelSlide: (value) {
if (value >= 0.98)
setState(() {
slidingPanelBorderRadius =
BorderRadius.vertical(top: Radius.circular(16));
});
},
onPanelClosed: () async {
setState(() {
listViewScrollingOff = true;
});
imageZoomController.value =
Matrix4.identity(); // so next Panel doesn't have zoomed in image
slidingPanelBorderRadius =
BorderRadius.vertical(top: Radius.circular(16));
},
minHeight: panelMinHeight,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (slideUpPanelScrollController) => _scrollingList(presentLocation, slideUpPanelScrollController),
body: ...
这是 _scrollingList 小部件:
Widget _scrollingList(LocationDetails presentLocation, ScrollController _slideUpPanelScrollController ) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: _slideUpPanelScrollController,
physics: listViewScrollingOff
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
key: Key("scrollingPanelListView"),
children: [
这是我的 Drawer ListView 项目中的 onTap:
onTap: () {
if(slidingPanelController.isPanelShown) {
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
大爱帮忙!下面是我认为的一个最小可行问题。我是用 Dartpad 写的,但是从 Dartpad 分享是很重要的,所以我把它复制并粘贴在这里。 Dartpad 无论如何都不支持该插件,因此您无法在那里进行调整。
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
PanelController slidingPanelController = new PanelController();
ScrollController slideUpPanelScrollController = new ScrollController();
final String title = "sliding panel";
String panelContent = "";
String stupidText = "";
String stupidText2 = ""
int panelMinHeight = 0;
int teaserPanelHeight = 77;
bool listViewScrollingOff = false;
initState() {
super.initState();
for(int i = 0; i < 500; i++) {
stupidText += "More stupid text. ";
}
for(int i = 0; i < 500; i++) {
stupidText2 += "More dumb, dumbest text. ";
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text(title)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
if(slidingPanelController.isPanelShown) {
print('attempting to scroll to top and close panel');
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Navigator.of(context).pop();
setState() {
panelContent = stupidText1;
panelMinHeight = teaserPanelHeight;
}
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
if(slidingPanelController.isPanelShown) {
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Navigator.of(context).pop();
setState() {
panelContent = stupidText2;
panelMinHeight = teaserPanelHeight;
}
}
),
],
),
),
body: SlidingUpPanel(
key: Key("slidingUpPanelKey"),
borderRadius: 8,
parallaxEnabled: false,
controller: slidingPanelController,
isDraggable: true,
onPanelOpened: () async {
setState(() {
listViewScrollingOff = false;
panelMinHeight = 0;
animatedMarkerMap;
//slideUpPanelScrollController.jumpTo(0);
});
},
onPanelSlide: (value) {
print("onPanelSlide: attempting to scroll panel");
},
onPanelClosed: () async {
setState(() {
//slideUpPanelScrollController.jumpTo(0);
listViewScrollingOff = true;
});
},
minHeight: panelMinHeight,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
// TODO BUG
// SAM, IF I USE PANELBUILDER's ScrollController attached to the panel's ListView, then, when closing, the ListView will move to the top first, then the panel closes,
// however ListView's controller is set to a globalController, this causes a bug when closing the panel, but means you can open/peek the panel from the App drawer,
panelBuilder: (slideUpPanelScrollController) => _scrollingList(panelContent, slideUpPanelScrollController),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
),
);
}
Widget _scrollingList(String panelContent, ScrollController _slideUpPanelScrollController ) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: _slideUpPanelScrollController,
physics: listViewScrollingOff
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
key: Key("scrollingPanelListView"),
children: [Text(panelContent)])));
}
}
好的,所以问题就变成了当我关闭滑动面板时 'naturally',通过将面板向上滚动到顶部然后向下滑动面板,两件事同时发生了。
我找到了解决方法,我需要将 SlidingUpPanel 的 isDraggable 属性 设置为 false,直到用户滚动到面板顶部。
像这样...
@override
void initState() {
super.initState();
slideUpPanelScrollController.addListener(() {
if(slideUpPanelScrollController.offset == 0) {
setState(() {
isDraggableBool = true;
});
}
});
}
这种方法的缺点是侦听器是 运行 每当面板滚动时它的测试,它会卡住滚动吗?有 better/clearer/more 高效的方法吗?
为了完成,我将 setScrollBehaviour
修改为:
void setScrollBehavior(bool _canScroll, {resetPos = false}) {
setState(() {
canScroll = _canScroll;
isDraggableBool = !_canScroll;
if (resetPos) {
slideUpPanelScrollController.jumpTo(0);
isDraggableBool = true;
}
});
}
所以当用户可以滚动时他们不能拖动。
当面板关闭时,resetPos == true
因此面板会滚动到顶部并且可以再次拖动(滑动)它。
ListView might be the issue here. You could try to jump to the top before closing the panel instead of after. You could also try to provide a key to your ListView so it forces flutter to re-render. Eventually, if it doesn't work wrap the body of the slide panel inside a SingleChildScrollview and disable ListView scrolling using physics. It's a bit difficult to visualize a fix though. If you can provide a link to DartPad这样就容易多了
====更新====
您可以克隆面板构建器的滚动行为并在其他地方自由使用控制器。这样,您就不必担心列表视图滚动物理或面板状态。
进行了一些清理工作,添加了评论,并修复了 tap/scrolling 个问题。
此方法的唯一缺点是用户必须滚动回顶部才能关闭面板(您始终可以用手势包裹 body 并在需要时点击调用 resetPanel)
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.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 PanelController panelController = PanelController();
ScrollController? _scrollController;
/// reset the content position of your listview
void resetScrollBehavior() {
// We make sure that our scroll exist and it is attached before reset
if (_scrollController != null && _scrollController!.hasClients) {
_scrollController!.jumpTo(0);
}
}
/// close the panel and reset the scroll behavior
void resetPanel() {
// We make sure our panel is attached and open before executing
if (panelController.isAttached && panelController.isPanelOpen) {
panelController.close();
// Remove this line if you wish to not reset the scrollExtent on panel reset.
resetScrollBehavior();
}
}
void onDrawerItemTap() {
// Reset the panel and pop the screen
resetPanel();
Navigator.of(context).pop();
}
@override
void dispose() {
// we make sure to dispose our controller(s)
_scrollController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
onDrawerItemTap();
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
onDrawerItemTap();
},
),
],
),
),
body: SlidingUpPanel(
controller: panelController,
borderRadius: BorderRadius.circular(8),
minHeight: 80,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (ScrollController sc) {
// ! So we can use local scrollcontroller outside the panel builder
_scrollController ??= sc;
return ScrollingList(
scrollController: _scrollController!,
);
},
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
}
class ScrollingList extends StatefulWidget {
const ScrollingList({
Key? key,
required this.scrollController,
}) : super(key: key);
final ScrollController scrollController;
@override
_ScrollingListState createState() => _ScrollingListState();
}
class _ScrollingListState extends State<ScrollingList> {
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: widget.scrollController,
children: List.generate(200, (index) => Text('Text #$index')),
),
),
);
}
}
===旧===
运行 测试了一下,好像是panelController.close();
也会触发 onPanelClosed();
所以你可以利用它来处理你的滚动行为。
以下示例演示了如何通过点击抽屉项目来重置面板 and/or 列表视图。
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.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> {
PanelController panelController = PanelController();
ScrollController scrollController = ScrollController();
bool canScroll = false;
void onDrawerItemTap() {
if (panelController.isAttached && panelController.isPanelOpen) {
panelController.close();
}
Navigator.of(context).pop();
}
void setScrollBehavior(bool _canScroll, {resetPos = false}) {
setState(() {
canScroll = _canScroll;
if (resetPos) {
scrollController.jumpTo(0);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
onDrawerItemTap();
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
onDrawerItemTap();
},
),
],
),
),
body: SlidingUpPanel(
controller: panelController,
borderRadius: BorderRadius.circular(8),
parallaxEnabled: false,
isDraggable: true,
onPanelOpened: () {
setScrollBehavior(true);
},
onPanelSlide: (_) {
// panel slide
},
onPanelClosed: () {
setScrollBehavior(false, resetPos: true);
},
minHeight: 80,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (_) => ScrollingList(
canScroll: canScroll,
scrollController: scrollController,
),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
}
class ScrollingList extends StatefulWidget {
const ScrollingList({
Key? key,
this.canScroll = false,
required this.scrollController,
}) : super(key: key);
final bool canScroll;
final ScrollController scrollController;
@override
_ScrollingListState createState() => _ScrollingListState();
}
class _ScrollingListState extends State<ScrollingList> {
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: widget.scrollController,
physics: !widget.canScroll
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
children: List.generate(200, (index) => Text('Text #$index')),
),
),
);
}
}
我正在使用 Flutter 的 sliding_up_panel 插件。
我想在从我的应用程序抽屉中选择新项目时将面板滚动到顶部。目前选择一个新项目会关闭面板并刷新面板内容。然后将其打开到 200px 峰值,但不会将面板的滚动位置重置为顶部。
我一直在兜圈子,以略有不同的方式尝试相同的解决方案,但一无所获。
我尝试过的: 我有全球
PanelController slidingPanelController = new PanelController();
ScrollController slideUpPanelScrollController = new ScrollController();
我尝试将我的全局 slideUpPanelScrollController
附加到面板的列表视图,但是当向上滑动面板的 ListView 时,它同时开始关闭整个面板。如果您向上滚动以阅读您浏览过的内容,那么,您将无法阅读,因为它正在消失。
防止此错误很容易,您可以按照插件示例中的规范方式进行操作,从 SlidingPanel
传递 ScrollController,然后在面板的 Listview 中创建一个本地 ScrollController。
panelBuilder: (slideUpPanelScrollController) => _scrollingList(presentLocation, slideUpPanelScrollController)
问题是,您无法在新的应用程序抽屉选择上滚动面板,因为控制器现在是本地的。
我尝试在本地列表视图 ScrollController 上放置一个侦听器,_slideUpPanelScrollController
并测试 panelController.close():
if(slidingPanelController.isPanelClosed) _slideUpPanelScrollController.jumpTo(0);
但是侦听器阻止了面板滑动,触发了滑动事件但面板没有滑动,或者也非常不情愿。
在 ListView 中途显示内容的面板中打开新选择的内容是一种故障用户体验。我会喜欢一些想法或更好的解决方案。
我需要它,所以当面板关闭时,我可以
slideUpPanelScrollController.jumpTo(0);
我需要将全局控制器附加到面板 ListView 的本地控制器,或者我需要一种方法来访问本地控制器以从我的 _scrollingList()
函数中触发它的 Scroll。
这是面板小部件:
SlidingUpPanel(
key: Key("slidingUpPanelKey"),
borderRadius: slidingPanelBorderRadius,
parallaxEnabled: false,
controller: slidingPanelController,
isDraggable: isDraggableBool,
onPanelOpened: () {
},
onPanelSlide: (value) {
if (value >= 0.98)
setState(() {
slidingPanelBorderRadius =
BorderRadius.vertical(top: Radius.circular(16));
});
},
onPanelClosed: () async {
setState(() {
listViewScrollingOff = true;
});
imageZoomController.value =
Matrix4.identity(); // so next Panel doesn't have zoomed in image
slidingPanelBorderRadius =
BorderRadius.vertical(top: Radius.circular(16));
},
minHeight: panelMinHeight,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (slideUpPanelScrollController) => _scrollingList(presentLocation, slideUpPanelScrollController),
body: ...
这是 _scrollingList 小部件:
Widget _scrollingList(LocationDetails presentLocation, ScrollController _slideUpPanelScrollController ) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: _slideUpPanelScrollController,
physics: listViewScrollingOff
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
key: Key("scrollingPanelListView"),
children: [
这是我的 Drawer ListView 项目中的 onTap:
onTap: () {
if(slidingPanelController.isPanelShown) {
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
大爱帮忙!下面是我认为的一个最小可行问题。我是用 Dartpad 写的,但是从 Dartpad 分享是很重要的,所以我把它复制并粘贴在这里。 Dartpad 无论如何都不支持该插件,因此您无法在那里进行调整。
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
PanelController slidingPanelController = new PanelController();
ScrollController slideUpPanelScrollController = new ScrollController();
final String title = "sliding panel";
String panelContent = "";
String stupidText = "";
String stupidText2 = ""
int panelMinHeight = 0;
int teaserPanelHeight = 77;
bool listViewScrollingOff = false;
initState() {
super.initState();
for(int i = 0; i < 500; i++) {
stupidText += "More stupid text. ";
}
for(int i = 0; i < 500; i++) {
stupidText2 += "More dumb, dumbest text. ";
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text(title)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
if(slidingPanelController.isPanelShown) {
print('attempting to scroll to top and close panel');
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Navigator.of(context).pop();
setState() {
panelContent = stupidText1;
panelMinHeight = teaserPanelHeight;
}
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
if(slidingPanelController.isPanelShown) {
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Navigator.of(context).pop();
setState() {
panelContent = stupidText2;
panelMinHeight = teaserPanelHeight;
}
}
),
],
),
),
body: SlidingUpPanel(
key: Key("slidingUpPanelKey"),
borderRadius: 8,
parallaxEnabled: false,
controller: slidingPanelController,
isDraggable: true,
onPanelOpened: () async {
setState(() {
listViewScrollingOff = false;
panelMinHeight = 0;
animatedMarkerMap;
//slideUpPanelScrollController.jumpTo(0);
});
},
onPanelSlide: (value) {
print("onPanelSlide: attempting to scroll panel");
},
onPanelClosed: () async {
setState(() {
//slideUpPanelScrollController.jumpTo(0);
listViewScrollingOff = true;
});
},
minHeight: panelMinHeight,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
// TODO BUG
// SAM, IF I USE PANELBUILDER's ScrollController attached to the panel's ListView, then, when closing, the ListView will move to the top first, then the panel closes,
// however ListView's controller is set to a globalController, this causes a bug when closing the panel, but means you can open/peek the panel from the App drawer,
panelBuilder: (slideUpPanelScrollController) => _scrollingList(panelContent, slideUpPanelScrollController),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
),
);
}
Widget _scrollingList(String panelContent, ScrollController _slideUpPanelScrollController ) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: _slideUpPanelScrollController,
physics: listViewScrollingOff
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
key: Key("scrollingPanelListView"),
children: [Text(panelContent)])));
}
}
好的,所以问题就变成了当我关闭滑动面板时 'naturally',通过将面板向上滚动到顶部然后向下滑动面板,两件事同时发生了。
我找到了解决方法,我需要将 SlidingUpPanel 的 isDraggable 属性 设置为 false,直到用户滚动到面板顶部。
像这样...
@override
void initState() {
super.initState();
slideUpPanelScrollController.addListener(() {
if(slideUpPanelScrollController.offset == 0) {
setState(() {
isDraggableBool = true;
});
}
});
}
这种方法的缺点是侦听器是 运行 每当面板滚动时它的测试,它会卡住滚动吗?有 better/clearer/more 高效的方法吗?
为了完成,我将 setScrollBehaviour
修改为:
void setScrollBehavior(bool _canScroll, {resetPos = false}) {
setState(() {
canScroll = _canScroll;
isDraggableBool = !_canScroll;
if (resetPos) {
slideUpPanelScrollController.jumpTo(0);
isDraggableBool = true;
}
});
}
所以当用户可以滚动时他们不能拖动。
当面板关闭时,resetPos == true
因此面板会滚动到顶部并且可以再次拖动(滑动)它。
ListView might be the issue here. You could try to jump to the top before closing the panel instead of after. You could also try to provide a key to your ListView so it forces flutter to re-render. Eventually, if it doesn't work wrap the body of the slide panel inside a SingleChildScrollview and disable ListView scrolling using physics. It's a bit difficult to visualize a fix though. If you can provide a link to DartPad这样就容易多了
====更新====
您可以克隆面板构建器的滚动行为并在其他地方自由使用控制器。这样,您就不必担心列表视图滚动物理或面板状态。
进行了一些清理工作,添加了评论,并修复了 tap/scrolling 个问题。
此方法的唯一缺点是用户必须滚动回顶部才能关闭面板(您始终可以用手势包裹 body 并在需要时点击调用 resetPanel)
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.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 PanelController panelController = PanelController();
ScrollController? _scrollController;
/// reset the content position of your listview
void resetScrollBehavior() {
// We make sure that our scroll exist and it is attached before reset
if (_scrollController != null && _scrollController!.hasClients) {
_scrollController!.jumpTo(0);
}
}
/// close the panel and reset the scroll behavior
void resetPanel() {
// We make sure our panel is attached and open before executing
if (panelController.isAttached && panelController.isPanelOpen) {
panelController.close();
// Remove this line if you wish to not reset the scrollExtent on panel reset.
resetScrollBehavior();
}
}
void onDrawerItemTap() {
// Reset the panel and pop the screen
resetPanel();
Navigator.of(context).pop();
}
@override
void dispose() {
// we make sure to dispose our controller(s)
_scrollController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
onDrawerItemTap();
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
onDrawerItemTap();
},
),
],
),
),
body: SlidingUpPanel(
controller: panelController,
borderRadius: BorderRadius.circular(8),
minHeight: 80,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (ScrollController sc) {
// ! So we can use local scrollcontroller outside the panel builder
_scrollController ??= sc;
return ScrollingList(
scrollController: _scrollController!,
);
},
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
}
class ScrollingList extends StatefulWidget {
const ScrollingList({
Key? key,
required this.scrollController,
}) : super(key: key);
final ScrollController scrollController;
@override
_ScrollingListState createState() => _ScrollingListState();
}
class _ScrollingListState extends State<ScrollingList> {
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: widget.scrollController,
children: List.generate(200, (index) => Text('Text #$index')),
),
),
);
}
}
===旧===
运行 测试了一下,好像是panelController.close(); 也会触发 onPanelClosed(); 所以你可以利用它来处理你的滚动行为。
以下示例演示了如何通过点击抽屉项目来重置面板 and/or 列表视图。
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.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> {
PanelController panelController = PanelController();
ScrollController scrollController = ScrollController();
bool canScroll = false;
void onDrawerItemTap() {
if (panelController.isAttached && panelController.isPanelOpen) {
panelController.close();
}
Navigator.of(context).pop();
}
void setScrollBehavior(bool _canScroll, {resetPos = false}) {
setState(() {
canScroll = _canScroll;
if (resetPos) {
scrollController.jumpTo(0);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
onDrawerItemTap();
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
onDrawerItemTap();
},
),
],
),
),
body: SlidingUpPanel(
controller: panelController,
borderRadius: BorderRadius.circular(8),
parallaxEnabled: false,
isDraggable: true,
onPanelOpened: () {
setScrollBehavior(true);
},
onPanelSlide: (_) {
// panel slide
},
onPanelClosed: () {
setScrollBehavior(false, resetPos: true);
},
minHeight: 80,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (_) => ScrollingList(
canScroll: canScroll,
scrollController: scrollController,
),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
}
class ScrollingList extends StatefulWidget {
const ScrollingList({
Key? key,
this.canScroll = false,
required this.scrollController,
}) : super(key: key);
final bool canScroll;
final ScrollController scrollController;
@override
_ScrollingListState createState() => _ScrollingListState();
}
class _ScrollingListState extends State<ScrollingList> {
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: widget.scrollController,
physics: !widget.canScroll
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
children: List.generate(200, (index) => Text('Text #$index')),
),
),
);
}
}