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

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

实现的效果是这样的:

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

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

猜你喜欢

  • 浅析Android 的 MediaPlayer类

    2021-06-01 01:50:08
  • java Lambda表达式的使用心得

    2023-08-18 05:59:51
  • Android Studio搜索功能(查找功能)及快捷键图文详解

    2021-10-23 08:47:20
  • C# PropertyGrid使用案例详解

    2022-03-18 20:51:51
  • Java反射机制基础详解

    2023-07-17 04:36:29
  • JavaWeb后台购物车类实现代码详解

    2023-11-09 23:11:26
  • Java中过滤器 (Filter) 和 拦截器 (Interceptor)的使用

    2023-07-07 00:20:28
  • SpringBoot实现单文件上传

    2023-10-01 21:43:42
  • Java和Ceylon对象的构造和验证

    2022-04-05 04:28:37
  • java开发CPU流水线与指令乱序执行详解

    2023-07-01 19:59:23
  • Kotlin协程概念原理与使用万字梳理

    2023-03-28 21:29:32
  • 详解C# 反射(Reflection)

    2021-09-08 14:42:59
  • Android自定义viewGroup实现点击动画效果

    2022-09-01 00:27:07
  • React Native与Android 原生通信的方法

    2021-08-11 19:02:56
  • Android自定义GestureDetector实现手势ImageView

    2023-12-18 05:16:35
  • Mybatis中的like模糊查询功能

    2023-09-25 11:57:28
  • Android读取手机通讯录联系人到自己项目

    2022-01-04 06:37:48
  • C#禁用双击窗体图标关闭窗体的方法

    2022-01-04 14:20:04
  • IDEA项目maven project没有出现plugins和Dependencies问题

    2021-08-08 10:59:53
  • Android 启动activity的4种方式及打开其他应用的activity的坑

    2023-12-25 01:18:48
  • asp之家 软件编程 m.aspxhome.com