软件编程
位置:首页>> 软件编程>> Android编程>> Flutter实现固定header底部滑动页效果示例

Flutter实现固定header底部滑动页效果示例

作者:Zuo  发布时间:2022-06-15 06:31:05 

标签:Flutter,header,固定底部,滑动页

实现的效果是这样的:

Flutter实现固定header底部滑动页效果示例

刚开始的时候,是在dev上找了两个轮子,简单测了下,都不太满意,滑动事件处理的比较粗糙,总有bug。就在想着,要不要拿源码改一版的时候,让我无意间看到了这个帖子

Flutter实现固定header底部滑动页效果示例

里面的想法,大开眼界,是通过 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 补充:

这是在上面功能基础上的一个小扩展,即当滑动距离超过一半则自动滚至顶部,反之回到底部,来看下效果:

Flutter实现固定header底部滑动页效果示例

思路也很简单,首先我要知道当前滚动的距离或其占比,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);
 }
}

来源:https://juejin.cn/post/7127686678587637796

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com