Flutter BottomSheet 三段式拖拽

news2025/1/23 6:21:42

BottomSheetBehavior

  • 追踪 BottomSheet
  • 系统默认实现效果
  • 准备要实现的功能点:
    • 定义三段式状态:BottomSheetBehavoir
    • 阀值定义
      • 1. 未达到滚动阀值,恢复状态
      • 2. 达到滚动阀值,更新状态

前面倒是有讲过Android原生的BottomSheetBehavior,使用场景还是蛮多的,最近在用Flutter做一款地图App,有用到BottomSheet的功能,但是 Flutter 自带的BottomSheet有点拉,只能显示和隐藏销毁,不支持折叠为最小高度状态也不支持三段式拖动,那就自己撸一个吧:

追踪 BottomSheet

既然是基于系统的BottomSheet ,不妨来看看sdk的实现方式,正常来讲,显示一个BottomSheet,可以通过showBottomSheet 来触发,或者给Scaffold配置bottomSheet属性,查看源码可以看到Scaffold.of(context).showBottomSheet,内部是创建了一个_StandardBottomSheet,继续追踪发现Widget其实是通过AnimatedBuilder来实现内容高度的扩展,其内部维护了一个BottomSheet。

简单阅读下BottomSheet源码,重点就在于 GestureDetector 的垂直方向上的手势回调 onVerticalDragUpdate 、以及onVerticalDragEnd,拖动位置更新、惯性滑动以及销毁,核心都在这了。
在这里插入图片描述

系统默认实现效果

  • 拖拽速度大于某一个像素阀值时,销毁。
  • 拖拽位置小于总高度的一半时,销毁。

保留这一份默认效果,对于想使用默认效果的同学,不做任何额外配置即可。

在这里插入图片描述

准备要实现的功能点:

  1. 三段式: 基于SDK的BottomSheet ,可扩展为完全展开、中间状态、折叠状态;
  2. 阻尼、惯性滑动: 支持配置最小滑动偏移量;
  3. 保持状态,支持Peek状态: 以最小高度显示BottomSheet;
  4. 打破 showBottomSheet 限制: 兼容系统默认的弹出方式,亦可当作正常的Widget使用,脱离showBottomSheet。

定义三段式状态:BottomSheetBehavoir

  • EXPANDED 完全展开
  • HALF 中间状态,介于EXPANDED与PEEK之间
  • PEEK 以一个最小高度展示
  • HIDDEN 完全隐藏,即销毁,系统默认效果

开启三段式,我们还需要配置一个约束条件,即BottomSheet的最大高度和最小高度 BoxConstraints:

  • 最小高度
    HALF模式下
    如果提供的 Constraints minHeight 小于最大高度的一半,则取后者,防止位置错乱!
var peekThreshold = enableHalf
     ? min(_childHeight / 2, constraints.minHeight) / _childHeight
     : constraints.minHeight / _childHeight;

阀值定义

  • 拖拽滚动阀值,大于此值,才允许滑动
    const double _offsetThreshold = 32.0;
  • 展开时最大高度 阀值
    const double _maxThreshold = 1.0;
  • 中间状态阀值
    const double _halfThreshold = 0.5;

当拖拽结束时,如果拖拽偏移量小于此阀值,则恢复状态,这里有个麻烦的点是需要根据用户拖拽方向来判断,是向上还是向下拖动。
方向判断可以在 _handleDragStart 回调时记录初始偏移量startY,_handleDragEnd 时计算开始和结束的差值

/// 偏移量
var offset = updateY-startY ;
/// 当前动画值
 var value = widget.animationController!.value;
 late double toValue;
 late BottomSheetBehavior mode;

offset<0 为向上滑动,反之 向下滑动。接下来需要根据滚动阀值来更新BottomSheet状态。

1. 未达到滚动阀值,恢复状态

  • 向上滑动,恢复BottomSheet状态: Expanded / Half / Peek
if (value >= _maxThreshold) {
   // 处于Expand状态,恢复
   toValue = _maxThreshold;
   mode = BottomSheetBehavior.EXPANDED;
 } else if (value > _halfThreshold && enableHalf) {
   // 处于Half,恢复
   toValue = _halfThreshold;
   mode = BottomSheetBehavior.HALF;
 } else {
   toValue = peekThreshold;
   mode = BottomSheetBehavior.PEEK;
 }
  • 向下滑动,恢复BottomSheet状态: Expanded / Half / Peek
if (value > _halfThreshold) {
   // 处于Expand状态,恢复
   toValue = _maxThreshold;
   mode = BottomSheetBehavior.EXPANDED;
 } else if (value > peekThreshold && enableHalf) {
   // 处于Half,恢复
   toValue = _halfThreshold;
   mode = BottomSheetBehavior.HALF;
 } else {
   toValue = peekThreshold;
   mode = BottomSheetBehavior.PEEK;
 }

2. 达到滚动阀值,更新状态

  • 向上滑动,更新BottomSheet状态: Expanded / Half / Peek
if (value > _halfThreshold) {
   toValue = _maxThreshold;
   mode = BottomSheetBehavior.EXPANDED;
 } else if (value > peekThreshold) {
   toValue = enableHalf ? _halfThreshold : _maxThreshold;
   mode = enableHalf ? BottomSheetBehavior.HALF : BottomSheetBehavior.EXPANDED;
 } else {
   toValue = peekThreshold;
   mode = BottomSheetBehavior.PEEK;
 }
  • 向下滑动,更新BottomSheet状态: Half / Peek
if (value > _halfThreshold) {
  toValue = enableHalf ? _halfThreshold : peekThreshold;
  mode = enableHalf ? BottomSheetBehavior.HALF : BottomSheetBehavior.PEEK;
} else {
  toValue = peekThreshold;
  mode = BottomSheetBehavior.PEEK;
}

以上,我们获取到了开始讲到的AnimatedBuilder的 动画值以及变化量,在**_handleDragEnd**中可以通过animateTo平滑的过渡BottomSheet状态

/// 以动画的形式fly
void animateTo(double to) {
  widget.animationController!.animateTo(
    to,
    curve: Curves.linearToEaseOut,
    duration: animateDuration,
  );
}
  • 另外,至于BottomSheet的最新的状态回调,最好是在动画结束后再通知给调用者,以免更新状态期间build重绘!
Future.delayed(animateDuration, () => widget.onBehaviorChanged?.call(mode));

至此,既保留了flutter默认的BottomSheet效果,又扩展了三段式,当然,调用方式和系统BottomSheet一模一样,另外还可以像普通Widget一样来使用哦,来看看最终的效果吧

RPReplay_Final1691646945

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/861694.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Camunda 7.x 系列【2】开源工作流引擎框架

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot 版本 2.7.9 本系列Camunda 版本 7.19.0 源码地址&#xff1a;https://gitee.com/pearl-organization/camunda-study-demo 文章目录 1. 前言2. 开源工作流引擎框架2.1 jBPM2.2 Activ…

综合技巧练习 - Packet Tracer 简介

1.7.1&#xff1a;综合技巧练习 - Packet Tracer 简介 拓扑图&#xff1a; 以基本完成的逻辑拓扑为起点。 设备 接口 IP 地址 子网掩码 默认网关 R1-ISP Fa0/0 192.168.254.253 255.255.255.0 不适用 S0/0/0 10.10.10.6 255.255.255.252 R2-Central Fa0/0 17…

DTC服务(0x14 0x19 0x85)

DTC相关的服务有ReadDTCInformation (19) service&#xff0c;ControlDTCSetting (85) service和ReadDTCInformation (19) service ReadDTCInformation (19) service 该服务允许客户端从车辆内任意一台服务器或一组服务器中读取驻留在服务器中的诊断故障代码( DTC )信息的状态…

并发——ThreadPoolExecutor 类简单介绍

文章目录 1 ThreadPoolExecutor 类分析2 推荐使用 ThreadPoolExecutor 构造函数创建线程池 线程池实现类 ThreadPoolExecutor 是 Executor 框架最核心的类。 1 ThreadPoolExecutor 类分析 ThreadPoolExecutor 类中提供的四个构造方法。我们来看最长的那个&#xff0c;其余三个…

P15 电路定理——巧妙-灵性-智慧

1、戴维南定理 2、戴维南定理的证明 对于线性含源的一个结构&#xff0c; 右边接一个支路N&#xff0c;再用电流源替代N 情况一&#xff1a;A没有独立源&#xff0c;那么A可以等价于一个电阻 情况二&#xff1a;A有独立源&#xff0c;例证法&#xff1a; 使用叠加法&#xf…

获取 Android 的 SHA1 值

1、调试版&#xff0c;可以直接在 Android studio 中的 gradle 中查看。也可以用下面方法进行 前提要先确定签名文件所在的路径&#xff1a;调试版默认使用的签名文件是debug.keystore&#xff0c;文件处于 C 盘用户目录下的.android文件夹下。打开命令行工具&#xff0c; 1、…

分析为何科研转化率低

最近这两天&#xff0c;[广西审计:高校1.31亿科研经费成果转化率为0] 话题引发热议。据报道&#xff0c;广西壮族自治区审计厅近日公布的《关于2022年度自治区本级预算执行和其他财政收支的审计工作报告》披露了广西在科教振兴资金审计方面 9 所高校开展科研的相关情况。报告发…

Flutter:文件上传与下载(下载后预览)

Dio dio是一个强大的Dart Http请求库&#xff0c;提供了丰富的功能和易于使用的API&#xff0c;支持文件上传和下载。 这个就不介绍了&#xff0c;网上有很多的封装案例。 background_downloader 简介 适用于iOS&#xff0c;Android&#xff0c;MacOS&#xff0c;Windows和L…

使用 prometheus client SDK 暴露指标

目录 1. 使用 prometheus client SDK 暴露指标1.1. How Go exposition works1.2. Adding your own metrics1.3. Other Go client features 2. Golang Application monitoring using Prometheus2.1. Metrics and Labels2.2. Metrics Types2.2.1. Counters:2.2.2. Gauges:2.2.3. …

【ECharts】树图

将3级改成4级 demo上是3层&#xff0c;如何实现4层。 initialTreeDepth: 4

【hello C++】特殊类设计

目录 一、设计一个类&#xff0c;不能被拷贝 二、设计一个类&#xff0c;只能在堆上创建对象 三、设计一个类&#xff0c;只能在栈上创建对象 四、请设计一个类&#xff0c;不能被继承 五、请设计一个类&#xff0c;只能创建一个对象(单例模式) C&#x1f337; 一、设计一个类&…

zynq复位管脚的方式

问&#xff1a;实际应用中&#xff0c;是不是PS_SRST_B必须&#xff0c;但是POR不是必须&#xff1f; 答&#xff1a;POR是必须有的&#xff0c;不然只能掉电复位&#xff0c;POR都是画上的。POR是整个芯片最高级复位&#xff0c;就是整个芯片都会复位。PS支持外部上电复位信号…

数据库新闻速递 -- POSTGRESQL 正在蚕食数据库市场 (翻译)

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加 liuaustin3微信号 &#xff0c;在新加的朋友会分到3群&#xff…

什么是DNS服务器的层次化和分布式?

DNS (Domain Name System) 的结构是层次化的&#xff0c;意味着它是由多个级别的服务器组成&#xff0c;每个级别负责不同的部分。以下是 DNS 结构的层次&#xff1a; 根域服务器&#xff08;Root Servers&#xff09;&#xff1a; 这是 DNS 层次结构的最高级别。全球有13组根域…

开发命名规范

1项目命名规范 1、工程项目名&#xff0c;尽量想一些有意义、有传播价值的名称&#xff1b;比如星球、游戏、名人、名地名等&#xff1b;取名就跟给孩子取名一样&#xff0c;独特、有价值、有意义、好传播 2、所有的类都必须添加创建者和创建日期 3、所有代码&#xff1a;包括…

vteamled透明屏,在商业领域中的显示效果怎么样?高清、亮度

vteamled透明屏是一种新型的显示屏技术&#xff0c;它采用透明材料制成&#xff0c;可以实现透明显示效果。vteamled透明屏具有以下特点&#xff1a; 首先&#xff0c;vteamled透明屏具有高透明度。 它采用高透明材料制成&#xff0c;可以达到80%以上的透明度&#xff0c;使得…

Laravel 框架数据库配置构造器的查询.分块.聚合 ⑤

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; THINK PHP &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

激活函数总结(四):Hard系列激活函数补充(HardSigmoid、HardTanh、Hardswish)

激活函数总结&#xff08;四&#xff09;&#xff1a;Hard系列激活函数补充 1 引言2 激活函数2.1 HardSigmoid激活函数2.2 HardTanh激活函数2.3 Hardswish激活函数 3. 总结 1 引言 在前面的文章中已经介绍了过去大家较为常见的激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PRe…

ruoyi-cloud微服务新建子模块

ruoyi-cloud微服务新建子模块 1、复制system模块 直接复制 modules下面已有的system模块&#xff0c;改名为 test 2、在modules下的 pom.xml文件中添加子模块 3、进入 test模块修改 pom.xml 把原有的system 修改成test 4、修改对应的包名、目录名和启动应用程序为test 5、修…

zustand:基于 Flux 模型实现的小型、快速和可扩展的状态管理

目录 ReactStep 1&#xff1a;安装Step 2&#xff1a;Store 初始化Step3&#xff1a;Store 绑定组件&#xff0c;就完成了!效果图 VueStep 1: 安装Step 2: Store 初始化Step 3: Store 绑定组件&#xff0c;就完成了!效果图 微前端为什么你需要 zustand-pub &#xff1f;安装ste…