当前位置: 首页 > news >正文

Flutter BottomSheet 三段式拖拽

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一样来使用哦,来看看最终的效果吧

项目效果

Demo

相关文章:

Flutter BottomSheet 三段式拖拽

BottomSheetBehavior 追踪 BottomSheet系统默认实现效果准备要实现的功能点&#xff1a;定义三段式状态&#xff1a;BottomSheetBehavoir阀值定义1. 未达到滚动阀值&#xff0c;恢复状态2. 达到滚动阀值&#xff0c;更新状态 前面倒是有讲过Android原生的BottomSheetBehavior&a…...

php后端实现调用高德地图进行POI搜索

对于当前位置或者选定省市位置进行查询 接口实现 /*** 查询地址* ApiTitle (查询地址)* ApiSummary (查询地址)* ApiMethod (POST)* ApiRoute (/api/demo/address)* ApiParams (name"dart", type"integer", requiredtrue, description"省…...

uniapp 实现滑动视图切换 顶部滚动导航栏

无论小程序的时候一般有这个功能,在页面处于首页时候,滑动视图,切换视图顶部滚动导航也跟着切换 1.想要实现这个功能就需要实现顶部导航栏,首先实现顶部滚导航栏 点击高亮颜色显示 模板代码 <scroll-view scroll-x"true" class"scroll-content" > …...

ArcGIS API for JavaScript 调用自定义地图模板总结

ArcGIS API for JavaScript 调用自定义地图模板总结 3.9版本4.24版本 3.9版本 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Hello World</title><link rel"stylesheet" href&qu…...

QGraphicsView实现简易地图5『经纬网格』

前文链接&#xff1a;QGraphicsView实现简易地图4『局部加载-地图漫游』 由于GCJ02 Web 墨卡托投影 纬度并不随像素等分&#xff0c;且两极跨度较大&#xff0c;因此本次演示采用的经纬网等分逻辑为等分像素。同等像素跨度之间&#xff0c;两级纬度变化较小&#xff0c;越靠近赤…...

RestTemplate 请求转发异常 ERR_CONTENT_DECODING_FAILED 200 (OK)

#1 问题描述 在基于Spring Boot的项目中实现了请求转发&#xff08;使用 RestTemplate 的 exchange 方法&#xff09;的功能&#xff0c;忽然在前端报net::ERR_CONTENT_DECODING_FAILED 200 (OK)的错误&#xff0c;后端及上游系统日志均显示请求已完成。 #2 原因探寻 上述错…...

用python实现一个异或计算器

有这样一条需求&#xff1a;计算某个文件中的数组每一行元素的最后一个参数&#xff0c;异或输出。 因为元素比较多&#xff0c;十几行&#xff0c;通过人工去计算异或值非常困难。 而在线异或的计算器&#xff0c;也需要人为输入这些数值&#xff0c;每次计算一个最终结果需…...

Sketch打不开AI文件?转换方法在这里

1、对比设计软件 Sketch 与 AI 软件功能 Sketch 与 Illustrator 都是行业内优秀的矢量图形设计软件&#xff0c;各有千秋。Sketch 从 2010 年面世&#xff0c;专注 APP 界面设计&#xff0c;深受初学者与专业人士喜爱。Illustrator 拥有更悠久的历史&#xff0c;是处理复杂图标…...

小游戏扫雷实现教学(详解)

目录 【前言】 一、模块化程序设计&#xff08;多文件编程&#xff09;介绍 1.概述 2.传统编程的方式 3.模块化程序设计的方法 二、扫雷代码设计思路 三、扫雷代码设计 1.创建菜单函数 2.实现9x9扫雷 3.初始化棋盘 4.打印棋盘 5.随机布置雷的位置 6.排查雷的信息 7.回…...

04 mysql innodb record

前言 最近看到了 何登成 大佬的 "深入MySQL源码 -- Step By Step" 的 pdf 呵呵 似乎是找到了一些 方向 之前对于 mysql 方面的东西, 更多的仅仅是简单的使用[业务中的各种增删改查], 以及一些面试题的背诵 这里会参照 MySQL Internals Manual 来大致的看一下 i…...

Centos7安装Docker

0.安装Docker Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道…...

Vue中如何更好地封装组件?

子组件接受父组件传递的事件 1.子组件使用事件名"$emit(父组件中传递的事件名,想给父组件传递的参数(可选))" click"$emit(click)" 2.子组件使用 v-on"$listeners" 父组件&#xff1a; <template><div id"app"><myCo…...

C语言的链表的相关操作

本变博客源于自己想复习一下C语言&#xff0c;所以便自己动手复习了一下链表的相关操作。做个人记录使用。 main.c #include <stdio.h> #include "list.h"int main() {student *a;printf("hello world\n") ;printf("----初始化列表----------\…...

Python3中typing模块

Python类型注解是Python 3.5版本之后引入的新特性&#xff0c;它可以让开发者在函数、变量等声明时为其指定类型。typing模型能够声明类型&#xff0c;防止运行时出现参数和返回值类型不符合的问题。 ### 1. 基本类型注解 def hello(name: str) -> str:return (Hello, na…...

C语言自动抓取淘宝商品详情网页数据,实现轻松高效爬虫

你是否曾经遇到过需要大量获取网页上的数据&#xff0c;但手动复制粘贴又太过费时费力&#xff1f;那么这篇文章就是为你而写。今天我们将会详细讨论如何使用C语言实现自动抓取网页上的数据。本文将会从以下8个方面进行逐步分析讨论。 1. HTTP协议的基本原理 在开始之前&…...

数据结构---跳表

目录标题 为什么会有跳表跳表的原理跳表的模拟实现准备工作find函数insert函数erase函数 测试效率比较 为什么会有跳表 在前面的学习过程中我们学习过链表这个容器&#xff0c;这个容器在头部和尾部插入数据的时间复杂度为O(1)&#xff0c;但是该容器存在一个缺陷就是不管数据…...

为什么Tomcat的NIO在读取body时要模拟阻塞?

文章首发地址 Tomcat的NIO完全可以以非阻塞方式处理IO&#xff0c;为什么在读取body部分时要模拟阻塞呢&#xff1f;在Tomcat的NIO读取HTTP请求时&#xff0c;为了保证请求的正确性和可靠性&#xff0c;需要模拟阻塞模式&#xff0c;这是因为servlet规范里定义了ServletInputSt…...

26 | 谷歌应用APP数据分析

基于kaggle公开数据集,对谷歌应用市场的APP情况进行数据探索和分析。 from kaggle: https://www.kaggle.com/lava18/google-play-store-apps 分析思路: 0、数据准备 1、数据概览 2、种类对Rating的影响 3、定价策略 4、因素相关性分析 5、用户评价 6、总结 0、数据准备 (…...

BFS 五香豆腐

题目描述 经过谢老师n次的教导&#xff0c;dfc终于觉悟了——过于腐败是不对的。但是dfc自身却无法改变自己&#xff0c;于是他找到了你&#xff0c;请求你的帮助。 dfc的内心可以看成是5*5个分区组成&#xff0c;每个分区都可以决定的的去向&#xff0c;0表示继续爱好腐败&…...

opencv实战项目 手势识别-手势控制键盘

手势识别是一种人机交互技术&#xff0c;通过识别人的手势动作&#xff0c;从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪&#xff08;定位手部关键点&#xff09; 2.opencv实战项目 实现手势跟踪并返回位置信息&#xff08;封装调用&am…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...