flutter开发实战-手势Gesture与ListView滚动竞技场的可滑动关闭组件

flutter开发实战-手势Gesture与ListView滚动竞技场的可滑动关闭组件

最近看到了一个插件,实现一个可滑动关闭组件。滑动关闭组件即手指向下滑动,组件随手指移动,当移动一定位置时候,手指抬起后组件滑出屏幕。

一、GestureDetector嵌套Container非ListView

如果要可滑动关闭,则需要手势GestureDetector,GestureDetector这里实现了onVerticalDragDown、onVerticalDragUpdate、onVerticalDragEnd,通过手势,更新AnimatedContainer的高度。

@overrideWidget build(BuildContext context) {Size screenSize = MediaQuery.of(context).size;return Column(mainAxisSize: MainAxisSize.min,children: <Widget>[GestureDetector(onVerticalDragDown: _onVerticalDragDown,onVerticalDragUpdate: _onVerticalDragUpdate,onVerticalDragEnd: _onVerticalDragEnd,child: AnimatedContainer(curve: Curves.easeOut,duration: Duration(milliseconds: 250),onEnd: () {_onAniPositionedEnd(context);},height: yBottomOffset + widget.displayHeight,width: screenSize.width,clipBehavior: Clip.hardEdge,decoration: const BoxDecoration(color: Colors.transparent,),child: widget.child,),),],);}

我们通过onVerticalDragUpdate来更新AnimatedContainer的高度height,

void _onVerticalDragUpdate(DragUpdateDetails details) {print("_onVerticalDragUpdate");if (details.delta.dy <= 0) {// 向上isDragDirectionUp = true;} else {// 向下isDragDirectionUp = false;}yBottomOffset -= details.delta.dy;if (yBottomOffset > 0.0) {yBottomOffset = 0.0;}if (yBottomOffset < -widget.displayHeight) {yBottomOffset = -widget.displayHeight;}setState(() {});}

当拖动手势结束之后,来检测是否是隐藏状态。

void _onVerticalDragEnd(DragEndDetails details) {print("_onVerticalDragEnd");if (yBottomOffset < -widget.displayHeight / 3) {// 隐藏移除yBottomOffset = -widget.displayHeight;isCompleteHide = true;} else {yBottomOffset = 0.0;isCompleteHide = false;}setState(() {});}

AnimatedContainer中有onEnd方法回调,当动画结束之后,在此方法回调中来处理是否pop等操作

void _onAniPositionedEnd(BuildContext context) {print("_onAniPositionedEnd");if (isCompleteHide) {// 隐藏了,则移除Navigator.of(context).pop();}}

DragBottomSheet2完整代码如下

import 'package:flutter/material.dart';class DragBottomSheet2 extends StatefulWidget {const DragBottomSheet2({super.key,required this.child,required this.displayHeight,});// childfinal Widget child;// 展示的child高度final double displayHeight;@overrideState<DragBottomSheet2> createState() => _DragBottomSheet2State();
}class _DragBottomSheet2State extends State<DragBottomSheet2> {bool? isDragDirectionUp;double yBottomOffset = 0.0;bool isCompleteHide = false;void _onVerticalDragDown(DragDownDetails details) {print("_onVerticalDragDown");}void _onVerticalDragUpdate(DragUpdateDetails details) {print("_onVerticalDragUpdate");if (details.delta.dy <= 0) {// 向上isDragDirectionUp = true;} else {// 向下isDragDirectionUp = false;}yBottomOffset -= details.delta.dy;if (yBottomOffset > 0.0) {yBottomOffset = 0.0;}if (yBottomOffset < -widget.displayHeight) {yBottomOffset = -widget.displayHeight;}setState(() {});}void _onVerticalDragEnd(DragEndDetails details) {print("_onVerticalDragEnd");if (yBottomOffset < -widget.displayHeight / 3) {// 隐藏移除yBottomOffset = -widget.displayHeight;isCompleteHide = true;} else {yBottomOffset = 0.0;isCompleteHide = false;}setState(() {});}void _onAniPositionedEnd(BuildContext context) {print("_onAniPositionedEnd");if (isCompleteHide) {// 隐藏了,则移除Navigator.of(context).pop();}}@overrideWidget build(BuildContext context) {Size screenSize = MediaQuery.of(context).size;return Column(mainAxisSize: MainAxisSize.min,children: <Widget>[GestureDetector(onVerticalDragDown: _onVerticalDragDown,onVerticalDragUpdate: _onVerticalDragUpdate,onVerticalDragEnd: _onVerticalDragEnd,child: AnimatedContainer(curve: Curves.easeOut,duration: Duration(milliseconds: 250),onEnd: () {_onAniPositionedEnd(context);},height: yBottomOffset + widget.displayHeight,width: screenSize.width,clipBehavior: Clip.hardEdge,decoration: const BoxDecoration(color: Colors.transparent,),child: widget.child,),),],);}
}

点击按钮弹出bottomSheet2代码如下

void showBottomSheet2(BuildContext context) {Size size = MediaQuery.of(context).size;double displayHeight = size.height - 88;showModalBottomSheet(context: context,isScrollControlled: true,builder: (ctx) {return DragBottomSheet2(displayHeight: displayHeight,child: Container(width: size.width,height: displayHeight,color: Colors.orangeAccent,child: Text('内容',style: TextStyle(color: Colors.black,),),),);},);}

效果图如下

在这里插入图片描述

二、GestureDetector嵌套ListView

GestureDetector嵌套ListView后,Flutter会根据竞技场Arena机制,通过一定逻辑选择一个组件胜出。
Flutter为了解决手势冲突问题,Flutter给开发者提供了一套解决方案。在该方案中,Flutter引入了Arena(竞技场)概念,然后把冲突的手势加入到Arena中并竞争,谁胜利,谁就获得手势的后续处理权。

Arena竞技场的原理请看https://juejin.cn/post/6874570159768633357

所以在GestureDetector嵌套ListView后,Flutter框架会将这些Gesture与ListView组件都加入竞技场,然后通过一定的逻辑选择一个组件胜出,通常同类组件嵌套时最内层的组件胜出,胜出的组件会处理接下来的move和up事件,其它组件则不会继续处理这些事件了。所以在GestureDetector嵌套ListView的场景中,由于是ListView最终胜出,所以后续的事件都交由ListView处理,而GestureDetector收不到后续的事件,也就不会响应用户的手势了。因此,我们解决这个问题的第一步就是要让GestureDetector在这种场景下也能收到后续的事件

参考请看https://zhuanlan.zhihu.com/p/680586251

我们需要根据GestureDetector真正处理用户手势事件的是内部的Recognizer,比如处理上下滑动的是VerticalDragGestureRecognizer而Recognizer在竞技场失败后也可以单方面宣布自己胜出这样即使在竞技场失败了,GestureDetector也能收到后续的手势事件
因此我们现定义一个单方面宣布胜出的Recognizer

class _MyVerticalDragGestureRecognizer extends VerticalDragGestureRecognizer {@overridevoid rejectGesture(int pointer) {// 单方面宣布自己胜出acceptGesture(pointer);}
}

我们需要将Recognizer加入到GestureDetector中,会用到RawGestureDetector

RawGestureDetector(gestures: {_MyVerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<_MyVerticalDragGestureRecognizer>(() => _MyVerticalDragGestureRecognizer(),(_MyVerticalDragGestureRecognizer recognizer) {recognizer..onStart = (DragStartDetails details) {}..onUpdate = (DragUpdateDetails details) {}..onEnd = (DragEndDetails details) {};}),},child: ...);

这时候当滚动ListView时候,也能收到手势事件了。

监听ListView的滚动,时候我们需要用到NotificationListener

 NotificationListener(  // 监听内部ListView的滑动变化onNotification: (ScrollNotification notification) {if (notification is OverscrollNotification && notification.overscroll < 0) {// 用户向下滑动,ListView已经滑动到顶部,处理GestureDetector的滑动事件} else if (notification is ScrollUpdateNotification) {// 用户在ListView中执行滑动动作,关闭外部GestureDetector的滑动处理} else {}return false;},child:  //ListView),

最后DragGestureBottomSheet完整代码如下

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_demolab/drag_sheet_controller.dart';class DragGestureBottomSheet extends StatefulWidget {const DragGestureBottomSheet({super.key,required this.child,required this.displayHeight,this.duration = const Duration(milliseconds: 200),this.openDraggable = true,this.autoNavigatorPop = true,this.onShow,this.onHide,});// childfinal Widget child;// 展示的child高度final double displayHeight;// 拖动动画时长durationfinal Duration duration;// 是否需要拖动final bool openDraggable;// 是否需要自动popfinal bool autoNavigatorPop;// This method will be executed when the solid bottom sheet is completely// opened.final void Function()? onShow;// This method will be executed when the solid bottom sheet is completely// closed.final void Function()? onHide;@overrideState<DragGestureBottomSheet> createState() => _DragGestureBottomSheetState();
}class _DragGestureBottomSheetState extends State<DragGestureBottomSheet> {bool? isDragDirectionUp;double yBottomOffset = 0.0;bool isDraggable = false;bool isCompleteHide = false;DragSheetController? dragSheetController;@overridevoid initState() {// TODO: implement initStatedragSheetController = DragSheetController();dragSheetController?.dispatch(widget.displayHeight);super.initState();}@overridevoid dispose() {// TODO: implement disposedragSheetController?.dispose();super.dispose();}void _onVerticalDragUpdate(data) {if (widget.openDraggable) {print("data.delta.dy:${data.delta.dy}");if (data.delta.dy <= 0) {// 向上isDragDirectionUp = true;} else {// 向下isDragDirectionUp = false;}yBottomOffset -= data.delta.dy;if (yBottomOffset > 0.0) {yBottomOffset = 0.0;}if (yBottomOffset < -widget.displayHeight) {yBottomOffset = -widget.displayHeight;}double height = widget.displayHeight + yBottomOffset;dragSheetController?.dispatch(height);}}void _onVerticalDragEnd(data) {if (widget.openDraggable) {// 根据判断是否隐藏与显示if (false == isDragDirectionUp) {if (yBottomOffset < -widget.displayHeight / 3) {// 隐藏移除yBottomOffset = -widget.displayHeight;isCompleteHide = true;} else {yBottomOffset = 0.0;isCompleteHide = false;}} else {yBottomOffset = 0.0;isCompleteHide = false;}double height = widget.displayHeight + yBottomOffset;dragSheetController?.dispatch(height);}}void _onAniPositionedEnd(BuildContext context) {// 动画结束print("_onAniPositionedEnd");if (isCompleteHide) {// 隐藏,则调用hidenif (widget.onHide != null) {widget.onHide!.call();}} else {// 显示,则调用showif (widget.onShow != null) {widget.onShow!.call();}}if (isCompleteHide && widget.autoNavigatorPop) {// 隐藏了,则移除Navigator.of(context).pop();}}@overrideWidget build(BuildContext context) {Size screenSize = MediaQuery.of(context).size;return Column(mainAxisSize: MainAxisSize.min,children: <Widget>[RawGestureDetector(gestures: {_MyVerticalDragGestureRecognizer:GestureRecognizerFactoryWithHandlers<_MyVerticalDragGestureRecognizer>(() => _MyVerticalDragGestureRecognizer(),(_MyVerticalDragGestureRecognizer recognizer) {recognizer..onStart = (DragStartDetails details) {}..onUpdate = (DragUpdateDetails details) {if (!isDraggable) {return;}_onVerticalDragUpdate(details);}..onEnd = (DragEndDetails details) {_onVerticalDragEnd(details);};}),},child: StreamBuilder(stream: dragSheetController?.streamData,initialData: widget.displayHeight,builder: (_, snapshot) {return AnimatedContainer(curve: Curves.easeOut,duration: widget.duration,onEnd: () {_onAniPositionedEnd(context);},height: snapshot.data,width: screenSize.width,clipBehavior: Clip.hardEdge,decoration: const BoxDecoration(color: Colors.transparent,),child: NotificationListener(// 监听内部ListView的滑动变化onNotification: (ScrollNotification notification) {if (notification is OverscrollNotification &&notification.overscroll < 0) {// 用户向下滑动,ListView已经滑动到顶部,处理GestureDetector的滑动事件isDraggable = true;} else if (notification is ScrollUpdateNotification) {// 用户在ListView中执行滑动动作,关闭外部GestureDetector的滑动处理isDraggable = false;} else {}return false;},child: widget.child,),);},),)],);}
}class _MyVerticalDragGestureRecognizer extends VerticalDragGestureRecognizer {@overridevoid rejectGesture(int pointer) {// 单方面宣布自己胜出acceptGesture(pointer);}
}

三、DragSheetController处理数据流

这里定义了DragSheetController来处理数据流,DragSheetController中包括streamController、subscription、streamSink、streamData

StreamBuilder是一个Widget,它依赖Stream来做异步数据获取刷新widget。
Stream是一种用于异步处理数据流的机制,它允许我们从一端发射一个事件,从另外一端去监听事件的变化.Stream类似于JavaScript中的Promise、Swift中的Future或Java中的RxJava,它们都是用来处理异步事件和数据的。Stream是一个抽象接口,我们可以通过StreamController接口可以方便使用Stream。

使用详情请查看https://brucegwo.blog.csdn.net/article/details/136232000

最后DragSheetController代码如下

import 'dart:async';/// 处理Stream、StreamController相关逻辑
class DragSheetController  {StreamSubscription<double>? subscription;//创建StreamControllerStreamController<double>? streamController = StreamController<double>.broadcast();// 获取StreamSink用于发射事件StreamSink<double>? get streamSink => streamController?.sink;// 获取Stream用于监听Stream<double>? get streamData => streamController?.stream;// Adds new values to streamsvoid dispatch(double value) {streamSink?.add(value);}// Closes streamsvoid dispose() {streamSink?.close();}
}

通过DragSheetController,当拖动时候高度发生变化时候会调用dispatch方法,dispatch来发射数据流,DragGestureBottomSheet中通过StreamBuilder来调整AnimatedContainer的高度。

最后调用使用DragGestureBottomSheet

我们使用showModalBottomSheet展示DragGestureBottomSheet时候

// 显示底部弹窗void showCustomBottomSheet(BuildContext context) {Size size = MediaQuery.of(context).size;double displayHeight = size.height - 88;showModalBottomSheet(context: context,isScrollControlled: true,builder: (ctx) {return DragGestureBottomSheet(displayHeight: displayHeight,autoNavigatorPop: true,openDraggable: true,onHide: () {print("onHide");},onShow: () {print("onShow");},child: Container(width: size.width,height: displayHeight,color: Colors.white,child: ScrollConfiguration(behavior: NoIndicatorScrollBehavior(),child: ListView.builder(itemCount: 20,physics: ClampingScrollPhysics(),itemBuilder: (context, index) {return GestureDetector(child: Container(width: size.width,height: 100,decoration: BoxDecoration(color: Colors.transparent,border: Border.all(color: Colors.black12,width: 0.25,style: BorderStyle.solid,),),child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('index -- $index'),SizedBox(width: 50,child: ClipOval(child:Image.asset("assets/images/hero_test.png")),),],),),onTap: () {Navigator.of(context).push(CupertinoPageRoute(builder: (BuildContext context) {return HeroPage();}));},);},),),),);},);}

效果图如下

在这里插入图片描述

https://brucegwo.blog.csdn.net/article/details/136241765

四、小结

flutter开发实战-手势Gesture与ListView滚动竞技场的可滑动关闭组件

学习记录,每天不停进步。

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

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

相关文章

大保司保费贵,是否物有所值?

《大保司保费贵&#xff0c;是否物有所值》 这是罗师兄的原创文章 预计8-9分钟读完 作者&#xff1a;罗师兄 微信号&#xff1a;luoyun515 当我们想要买一份重疾险、储蓄险等长期险时&#xff0c; 我们会发现&#xff0c;同样的保障责任和保额&#xff0c; 不同保险公司的…

基于AdaBoost算法的情感分析研究-微博情感分析-文本分类

基于AdaBoost算法的情感分析研究 摘 要 随着互联网的快速发展&#xff0c;各类社交媒体平台如微信、QQ等也与日俱增&#xff0c;而微博更是集成了传统网站、论坛、博客等的优点&#xff0c;并加上了人与人之间的互动性、关系亲密程度等多种智能算法&#xff0c;并以简练的形式…

python安装与配置2024最新版

对python不熟悉的可以去看看这篇文章python介绍 pytho安装 来到Python官网&#xff1a;https://www.python.org/ 然后 选着download列表下的windows , 然后加进入python各个版本的下载界面 可以看到截止2024年2月22日,最新版是3.12.2 但是我们一般下载稳定版 如下,点击连接进…

解决两个MySQL5.7报错

目录 1.启动不了MySQL&#xff0c;报错缺少MSVCR120.dll去官网下载vcredist_x64.exe运行安装进入管理员CMD 2.本地计算机 上的 mysql 服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止&#xff0c;Fatal error: Can‘t open and lock privilege tables: Table ‘…

实习日志28

1.医院账套系统换新&#xff0c;卡片数据转移 1.1.修改旧导出的Excel 1.2.尝试导入新系统 1.3.修改导入数据再次导入即可 这个系统做的限制条件比较多&#xff0c;代码健壮性不错。 先在Excel表格里改好批量的&#xff0c;再导入检查&#xff0c;改一些细节的比较快捷。 2.…

ChatGPT回答模式

你发现了吗&#xff0c;ChatGPT的回答总是遵循这些类型方式。 目录 1.解释模式 2.类比模式 3.列举模式 4.限制模式 5.转换模式 6.增改模式 7.对比模式 8.翻译模式 9.模拟模式 10.推理模式 1.解释模式 ChatGPT 在回答问题或提供信息时&#xff0c;不仅仅给出…

【Linux取经路】文件系统之缓冲区

文章目录 一、先看现象二、用户缓冲区的引入三、用户缓冲区的刷新策略四、为什么要有用户缓冲区五、现象解释六、结语 一、先看现象 #include <stdio.h> #include <string.h> #include <unistd.h>int main() {const char* fstr "Hello fwrite\n"…

【思扬赠书 | 第3期】由面试题“Redis是否为单线程”引发的思考

⛳️ 写在前面参与规则&#xff01;&#xff01;&#xff01; ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论三次&#xff09; ⛳️本次送书1~4本【取决于阅读量&#xff0c;阅读量越多&#xff0c;送的越多】 很多人都遇到…

gitlab的使用

前一篇文章我们已经知道Git人人都是中心&#xff0c;那他们怎么交互数据呢&#xff1f; • 使用GitHub或者码云等公共代码仓库 • 使用GitLab私有仓库 目录 一、安装配置gitlab 安装 初始化 这里初始化完成以后需要记住一个初始密码 查看状态 二、使用浏览器访问&#xf…

Math方法,以及三角函数计算

abs(x) 返回参数的绝对值 var xMath.abs(-5) //5floor(x) 向下舍入为最接近的整数。 var xMath.floor(2.1) //2ceil(x) 向上舍入为最接近的整数。 var xMath.ceil(2.1) //3fround(x) 最接近的&#xff08;32 位单精度&#xff09;浮点表示。 var xMath.fround(2.60) //2.59…

小凡爬楼梯

解法&#xff1a; dp[i]:到第i阶梯&#xff0c;总共dp[i]种方案 状态转移方程&#xff1a; base condition: #include<iostream> #include<vector> #include<algorithm> using namespace std; #define endl \n int main() {vector<long long> dp(51…

板块一 Servlet编程:第四节 HttpServletResponse对象全解与重定向 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第四节 HttpServletResponse对象全解与重定向 一、什么是HttpServletResponse二、响应数据的常用方法三、响应乱码问题字符流乱码字节流乱码 四、重定向&#xff1a;sendRedirect请求转发和重定向的区别 在上一节中&#xff0c;我们系统的学习了…

黑色金属冶炼5G智能工厂数字孪生可视化管控系统,推进金属冶炼行业数字化转型

黑色金属冶炼5G智能工厂数字孪生可视化管控系统&#xff0c;推进金属冶炼行业数字化转型。随着科技的不断发展&#xff0c;数字化转型已经成为各行各业发展的必然趋势。金属冶炼行业作为传统工业的重要组成部分&#xff0c;也面临着数字化转型的挑战和机遇。为了推进金属冶炼行…

在 Windows 上使用 VC++ 编译 OpenSSL 源码的步骤

在 Windows 上使用 VC 编译 OpenSSL 源码的步骤如下&#xff1a; 准备工作 安装 Visual Studio 2017 或更高版本。安装 Perl 脚本解释器。安装 NASM 汇编器。 编译步骤 下载 OpenSSL 源码。解压 OpenSSL 源码。打开命令行工具&#xff0c;并进入 OpenSSL 源码目录。运行以下…

IT资讯——全速推进“AI+鸿蒙”战略布局!

文章目录 每日一句正能量前言坚持长期研发投入全速推进“AI鸿蒙”战略 人才战略新章落地持续加码核心技术生态建设 后记 每日一句正能量 人总要咽下一些委屈&#xff0c;然后一字不提的擦干眼泪往前走&#xff0c;没有人能像白纸一样没有故事&#xff0c;成长的代价就是失去原来…

2023 龙蜥操作系统大会演讲实录:《兼容龙蜥的云原生大模型数据计算系统——πDataCS》

本文主要分三部分内容&#xff1a;第一部分介绍拓数派公司&#xff0c;第二部分介绍 πDataCS 产品&#xff0c;最后介绍 πDataCS 与龙蜥在生态上的合作。 杭州拓数派科技发展有限公司&#xff08;简称“拓数派”&#xff0c;英文名称“OpenPie”&#xff09;是国内基础数据计…

论文发表 | 顶会顶刊的实验是如何炼成的

前言:Hello大家好,我是小哥谈。在计算机科学研究领域,尤其是当你追求顶级会议和期刊的发表时,没有什么⽐实验设计更关键了。为什么这么说?理由很简单。实验不仅仅是你⽤来 检验假设的⼿段,它更是审稿⼈会重点关注和阅读的部分,也是你验证⾃⼰研究多么创新、多么重要的内…

Linux之用户和用户组

目录 一、简介 1.1 用户账号分类 二、用户 2.1 useradd 2.2 userdel 2.3 usermod 2.4 passwd 2.5 su 2.6 登出 三、用户组 3.1 groupadd 3.2 groupdel 3.3 groupmod 3.4 newgrp 四、用户账号系统 4.1 /ect/passwd 4.2 常见的伪用户如下所示 五、思维导图 …

igolang学习3,golang 项目中配置gin的web框架

1.go 初始化 mod文件 go mod init gin-ranking 2.gin的crm框架 go get -u github.com/gin-gonic/gin 3.go.mod爆红解决

靡语IT:JavaScript_概述、基础

一、JavaScript 概述 javaScript 语言主要是完成页面的数据验证&#xff0c;因此它运行在客户端&#xff0c; 需要运行浏览器来解析执行 JavaScript 代码。js 是网景公司 &#xff08;Netscape&#xff09;的产品&#xff0c;最早取名为 LiveScript 最后借 java 的热度 改为 j…