Flutter Flow实现滑动显隐层示例详解
作者:张风捷特烈 发布时间:2022-01-29 23:50:56
1. 前言
最近要实现一个小需求,涵盖了很多知识点,比如手势、动画、布局等。挺有意思的,写出来和大家分享一下。如下所示,分为上下两层;当左右滑时,上层会随偏移量而平移,从而让上层产生滑动手势显隐的效果:
标题
这里上层通过不透明度 0.2 的蓝色示意,实际使用时可以改为透明色。很多直播间的浮层就是这种交互逻辑,通过右滑来隐藏浮层。
直播 | 右滑中 |
---|---|
2. 实现思路
思路其实非常简单,监听横向滑动的手势事件,根据偏移量让上层组件进行偏移。当放手时,根据偏移量是否达到宽度的一半,使用动画进行移出或者关闭。
偏移的实现方式有很多,但需要自由地进行布局和矩阵变换、透明度,并且需要支持动画的变化,Flow 组件是一个非常不错的选择。 Flow 组件可以通过代理类对子组件进行自定义布局,灵活性极强;如果是 CustomPaint
是 绘制之王 可以绘制万物,那么 Flow
就是 布局之王,可以摆放万物。三年前写过一篇介绍 Flow 使用的文章: 《【Flutter高级玩法- Flow 】我的位置我做主》 。 本文就不对 Flow 的基础使用进行介绍了。
另外,在滑动过程中需要注意限制偏移量,使偏移量在 0~size.width
之内;当放手时,通过动画控制器来驱动动画,使用补间让偏移量运动到 0
(打开) 或 size.width
(关闭) 。当关闭时,在右下角展示一个按钮用于点击展开:
3. 布局的代码实现
Flow 组件布局最重要的是实现 FlowDelegate
,在其中的 paintChildren
方法中实现布局的逻辑。和 CustomPainter
类似,FlowDelegate
的实现类也可以通过 super
构造为 repaint
入参设置可监听对象。可监听对象的变化会触发 paintChildren
重新绘制:
SwipeFlowDelegate
实现类再构造时传入可监听对象 offsetX
,在绘制索引为 1
的孩子时,通过 Matrix4
进行偏移。这样只要在手势水平滑动中,更新 offsetX 值即可。另外,可以根据 offsetX.value
是否达到 size.width
知道是否是关闭状态,如果已经关闭,绘制按钮。
class SwipeFlowDelegate extends FlowDelegate {
final ValueListenable<double> offsetX;
SwipeFlowDelegate(this.offsetX) : super(repaint: offsetX);
@override
void paintChildren(FlowPaintingContext context) {
Size size = context.size;
context.paintChild(0);
Matrix4 offsetM4 = Matrix4.translationValues(offsetX.value, 0, 0);
context.paintChild(1, transform: offsetM4);
// 偏移量对于父级尺寸
if (offsetX.value == size.width) {
Matrix4 m1 = Matrix4.translationValues(size.width / 2 - 30, size.height / 2 - 30, 0);
context.paintChild(2, transform: m1);
Matrix4 m2 = Matrix4.translationValues(size.width / 2 - 30, -(size.height / 2 - 50), 0);
context.paintChild(3, transform: m2);
}
}
@override
bool shouldRepaint(covariant SwipeFlowDelegate oldDelegate) {
return oldDelegate.offsetX.value != offsetX.value;
}
}
从这里可以看出,FlowDelegate
的最大优势是可以自定义孩子的绘制与否,还可以在绘制时通过 Matrix4
对孩子进行矩阵变换,还有可选参数可以控制透明度。接下来使用 Flow
组件时,提供 SwipeFlowDelegate
,并在 children 列表中依次放入子组件。其中前两个组件由外界传入,分别是底组件和上层组件,这样组件的布局就完成了,接下来监听事件,更新 factor
即可:
final ValueNotifier<double> factor = ValueNotifier(0);
Flow(
delegate: SwipeFlowDelegate(factor),
children: [
widget.content,
widget.overflow,
GestureDetector(
onTap: open,
child: const Icon(Icons.menu_open_outlined, color: Colors.white)),
GestureDetector(
onTap: () {
Navigator.of(context).pop();
},
child: const Icon(Icons.close, color: Colors.white))
],
)
4. 手势的监听
这里手势的处理是非常简单的,通过 GestureDetector
监听水平拖拽事件。在 onHorizontalDragUpdate
中根据拖拽的偏移量更新 factor
的值,其中通过 .clamp(0, widget.width)
可以限制偏移量的取值区间。
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragUpdate: _onHorizontalDragUpdate,
onHorizontalDragEnd: _onHorizontalDragEnd,
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: widget.width,
child: Flow( delegate:// 同上,略...
);
}
void _onHorizontalDragUpdate(DragUpdateDetails details) {
double cur = factor.value + details.delta.dx;
factor.value = cur.clamp(0, widget.width);
}
void _onHorizontalDragEnd(DragEndDetails details) {
if (factor.value > widget.width / 2) {
close();
} else {
open();
}
}
最后在 _onHorizontalDragEnd
回调中,根据当前偏移量是否大于一般宽度,决定关闭还是打开。期间过程使用动画进行偏移量的过渡变化。
5. 动画的使用
动画的使用,主要是通过 AnimationController
动画控制器来驱动数值的变化;在放手时 Tween
创建补间动画器,监听动画器数值的变化更新偏移量。这样偏移量就可以在指定时间内,在两个值之间渐变,从而产生动画效果。比如抬手时,open
方法是让偏移量从当前位置变化到 0
:
class _ScrollHideWrapperState extends State<ScrollHideWrapper> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
final ValueNotifier<double> factor = ValueNotifier(0);
@override
void initState() {
super.initState();
_ctrl = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
}
@override
Widget build(BuildContext context) {
// 略同...
}
// 动画关闭
Future<void> close() async {
Animation<double> anim = Tween<double>(begin: factor.value, end: widget.width).animate(_ctrl);
anim.addListener(() => factor.value = anim.value);
await _ctrl.forward(from: 0);
}
// 动画打开
Future<void> open() async {
Animation<double> anim = Tween<double>(begin: factor.value, end: 0).animate(_ctrl);
anim.addListener(() => factor.value = anim.value);
await _ctrl.forward(from: 0);
}
}
如果想让动画的变化非匀速,可以使用 Curve 来控制动画曲线。这样,基于 Flow 实现的自定义布局,就可以根据手势和动画,完成特定的交互功能。从这里可以看出 Flow 自定义布局的灵活性非常强,很多疑难杂症,都可以使用它来完成。
比如企业微信中:侧滑展示左栏,而且上层不会全部消失,通过 Flow 来自定义布局就很容易实现。大家可以基于本文,自己实现一下作为练习。那本文就到这里,谢谢观看 ~
标题 | 关闭 |
---|---|
来源:https://juejin.cn/post/7205959141562023997


猜你喜欢
- 对象重复是指对象里面的变量的值都相等,并不定是地址。list集合存储的类型是基础类型还比较好办,直接把list集合转换成set集合就会自动去
- 前言我们都知道在java中进行日期格式化使用simpledateformat。通过格式 yyyy-MM-dd 等来进行格式化,但是你知道其中
- 公司的svn的地址改变了,怎么办呢。自己本地的正在修改的项目怎么办呢?修改一下svn的服务器地址咯。1.就是先关闭ide,重新打开,然后选择
- 问题(1)重入锁是什么?(2)ReentrantLock如何实现重入锁?(3)ReentrantLock为什么默认是非公平模式?(4)Ree
- 方法的递归调用1. 基本介绍:简单地说,递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂问题的同时让代码变得简洁
- 在后端数据接口项目开发中,经常遇到返回的数据中有null值,导致前端需要进行判断处理,否则容易出现undefined的情况,如何便捷的将nu
- 1. 什么是AOPAOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期 * 实现在
- 几点重要的用法:a 先来介绍几个方法TimeSpan.Minutes(其他时间比如天数,小时数,秒数都一样的情况下得到的分钟数的差),其他的
- 废话不多说,上代码public String getRelativeTimeSpanStringForIphone(long time,lo
- 很多时候需要先判断当前用户的网络,才会继续之后的一些处理逻辑。但网络类型获取这一块,我用我自己的的手机调试时遇到一些问题,这里记录一下。一加
- 1、 流的继承关系,以及字节流和字符流。2、 节点流FileOutputStream和FileInputStream和处理流Buffered
- 去年谷歌 I/O大会上介绍了一个非常厉害的新框架DataBinding, 数据绑定框架给我们带来了很大的方便,以前我们可能需要在每个Acti
- 找了很久查询objectid的方法都是错的,用mongovue能查询出来,但就是用java不知道怎么查询1.mongovue里的查询方式:{
- 本文主要给大家介绍了关于RxJava的一些特殊用法,分享出来供大家参考学习,需要的朋友们下面来一起看看吧。一、按钮绑定通过 RxView 可
- 前言Spring框架对Bean进行装配提供了很灵活的方式,下面归纳一下主要的方式:在XML中进行显示配置在Java中进行显示配置隐式的bea
- 在开发中,我们通常需要将从数据库中查询的集合数据转换成类似文件系统一样的树形集合,比如:省市单位,部门机构,书籍分类等TreeNode对象@
- 求数组中最大的数的值:1、数组的max函数:class Program { &nb
- 首先需要有网络权限,然后我们这里匹配的网络请求是之前封装好的Okhttp。非常的简单方便,直接复制进去,依赖一下包,然后调用方法即可。 这里
- 应用场景我们开发的控制台应用,在运行阶段很有可能被用户Ctrl+C终止或是被用户直接关闭。如果我们不希望用户通过Ctrl+C终止我们的程序,
- 1 MyBatisPlusConfigMyBatisPlus配置类。package com.config;import