Flutter实现固定header底部滑动页效果示例
Zuo 人气:0正文
实现的效果是这样的:
刚开始的时候,是在dev上找了两个轮子,简单测了下,都不太满意,滑动事件处理的比较粗糙,总有bug。就在想着,要不要拿源码改一版的时候,让我无意间看到了这个帖子
里面的想法,大开眼界,是通过 DraggableScrollableSheet 和 IgnorePointer 来完美实现上面的效果。
实现
这是 DraggableScrollableSheet 的代码,
DraggableScrollableSheet( maxChildSize: 0.8, minChildSize: 0.25, // 注意都是占父组件的比例 initialChildSize: 0.25, expand: true, builder: (BuildContext context, ScrollController controller) { return Stack(); // body列表和header栏都在stack内 }, )
这是 body 列表和 header,这里的 body 是个 list,
Stack( children: [ Container( color: Colors.blue, child: Body( // ListView.separated controller: controller, paddingTop: headerHeight, // 防止压盖 ), ), const IgnorePointer( // 这里不接收事件,所以拖动 header 也能够滑动页面 child: Header( // Container[Center[Text]] height: headerHeight, ), ), ], )
但如果我们想在 header 内加点击事件呢?那在 Stack header 上层再加 widget 就好了。
代码就这点,我放在了 gitHub 上,感兴趣的可以看下。
2022.8.23 补充:
这是在上面功能基础上的一个小扩展,即当滑动距离超过一半则自动滚至顶部,反之回到底部,来看下效果:
思路也很简单,首先我要知道当前滚动的距离或其占比,DraggableScrollableController 提供了这个能力:
void _draggableScrollListener() { // [_currStale] 记录下当前的占比 // [_controller.size] 即占比, 范围[minChildSize,maxChildSize] // [_controller.pixels] 即距离 if (_currStale != _controller.size) { _currStale = _controller.size; } debugPrint('[listener] size: ${_controller.size}' ', pixels : ${_controller.pixels}'); }
其次要知道用户何时停止了滚动,我们可以使用 NotificationListener 来监听 DraggableScrollableSheet 的滚动状态:
NotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification) { ... return false; }, child: DraggableScrollableSheet(...),
之后在用户停止滚动的时候,我们判断当前距离,并根据结果让 DraggableScrollableSheet 自动滚动到顶部或底部。
onNotification: (ScrollNotification notification) { if (_animation) { // 动画中,不处理状态 return false; } if (notification is ScrollStartNotification) { debugPrint('start scroll'); } else if (notification is ScrollEndNotification) { debugPrint('stop scroll'); // 通过 [_controller.animateTo] 方法滚动 _scrollAnimation(); } return false;
在 _scrollAnimation 内就是滚动的方法了,这里要注意的是,不能直接使用 await Feature,我测试在频繁不同方向滑动时,可能会导致方法被挂起。在这直接 dedelayed(duration: xx) 即可:
Future<void> _scrollAnimation() async { if (_animation) { return; } _animation = true; //debugPrint('async start'); final int duration; // `await`ing the returned Feature(of [animateTo]) may cause the method to hang // So, Start a timer to set [_animation]. if (_currStale >= (_maxScale + _minScale) * 0.5) { duration = (_duration * ((_maxScale - _currStale) / (_maxScale - _minScale))) .toInt(); if (duration == 0) { _animation = false; return; } else { // [duration] control speed, Avoid situations where it's equal to 0 _animationTo(_maxScale, duration); } } else { duration = (_duration * ((_currStale - _minScale) / (_maxScale - _minScale))) .toInt(); if (duration == 0) { _animation = false; return; } else { _animationTo(_minScale, duration); } } Future.delayed( Duration(milliseconds: duration), ).then((value) => { //debugPrint('async stop'), _animation = false, }); }
其中 _animationTo
是实际控制控件滚动的方法:
void _animationTo(double scale, int duration) { _controller.animateTo( scale, duration: Duration(milliseconds: duration), curve: Curves.ease, ); }
2022.9.24 补充:
那如果再提供一种通过点击按钮来控制 DraggableScrollableSheet
收起和弹出的方法呢?
我们可以直接这样,是不是很简单:
Future<void> _scrollAnimation2() async { if (_animation) { return; } if (_currStale > (_maxScale + _minScale) * 0.5) { _animationTo(_minScale, _duration); } else { _animationTo(_maxScale, _duration); } }
加载全部内容