5G到来,App的未来,是JavaScript,Flutter还是Native ?
栏目: JavaScript · 发布时间: 5年前
内容简介:Flutter最近比较热门,但是Flutter成体系的文章并不多,前期避免不了踩坑;我这篇文章主要介绍如何使用Flutter实现一个比较复杂的手势交互,顺便分享一下我在使用Flutter过程中遇到的一些小坑,减少大家入坑;作者:HitenDev 链接:对了,顺便分享一下生成
Flutter最近比较热门,但是Flutter成体系的文章并不多,前期避免不了踩坑;我这篇文章主要介绍如何使用Flutter实现一个比较复杂的手势交互,顺便分享一下我在使用Flutter过程中遇到的一些小坑,减少大家入坑;
作者:HitenDev 链接: www.jianshu.com/p/4d1e81ab3…
先睹为快
本项目支持ios&android运行,效果如下
对了,顺便分享一下生成 gif 的小窍门,建议用手机自带录屏功能导出 mp4 文件到电脑,然后电脑端用 ffmpeg 命令行处理,控制 gif 的质量和文件大小,我的建议是分辨率控制在270p,帧率在10左右;
交互分析
看文章的小伙伴最好能手持即刻App,亲自体验一下探索页的交互,是黄色Logo黄色主题色的即刻;有人称‘黄即’;
即刻App原版功能有卡片旋转,卡片撤回和卡片自动移除,时间关系暂时没有实现,但核心的功能都在;
从一个Android开发者的习惯来看待,这个交互可拆分内外两层控件,外层我们需要一个整体下拉的控件,我称为 下拉控件 ;内层我们需要实现一个上、下、左、右四方向拖拽移动的控件,我们称为 卡片控件 ; 下拉控件 和 卡片控件 不仅要处理手势,还需要处理子Widget的布局;下面我再分析细节功能:
下拉控件:
- 子控件从上到下竖直摆放,顶部菜单默认隐藏在屏幕外
- 下拉手势所有子控件下移,菜单视觉差效果
- 支持点击自动展开、收起效果
卡片控件
- 卡片层叠布局,错落有致
- 最上层卡片支持手势拖拽
- 其他卡片相应拖拽小幅位移
- 松手移除卡片
码上入手
热身
套用App开发伎俩,实现上面的交互无非就是控件布局和手势识别。当然Flutter开发也是这些套路,只不过万物皆是Widget,在Flutter中常用的基本布局有 Column 、 Row 、 Stack 等,手势识别有 Listener 、 GestureDetector 、 RawGestureDetector 等,这是本文重点讲解的控件,不限于上面这几个Widget,因为Flutter提供的Widget太多了,重点的控件需要牢记外,其他时候真是现用现查;
所以下面我们从布局和手势这两个大的技术点,来一一击破功能点;
布局摆放
这里所谓的布局,包括Widget的尺寸大小和位置的控制,一般都是父Widget掌管子Widget的命运,Flutter就是一层一层Widget嵌套,不要担心,下面从外到内具体案例讲解;
下拉控件
首先我们要实现最外层布局,效果是:子Widget竖直摆放,且最上面的Widget默认需要摆放在屏幕外;
如上图所示,红色区域是屏幕范围, header 是头部隐藏的菜单布局, content 是卡片布局的主体;
先说入的坑
竖直布局我最先想到的是 Column ,我想要的效果是 content 高度和父Widget的高度一致,我首先想到是让 Expanded 包裹 content ,结果是content的高度永远等于 Column 高度减 header 高度,造成现象就是content高度不填充,或者是挤压现象,如果继续使用 Colunm 可能就得放弃 Expanded ,手动给 content 赋值高度,没准是个办法,但我不愿意手动赋值 content 的高度,太不优雅了,最后果断弃用 Column ;
另一个问题是如何隐藏 header ,我想到两种方案:
- 采用外层 Transform 包裹整个布局,内层 Transform 包裹 header ,然后赋值内层 dy = -headerHeight ,随着手势下拉动态,并不改变 header 的 Transform ,而是改变最外层 Transform 的 dy ;
- 动态改变 header 高度,初始高度为0,随着手势下拉动态计算;
但是上面这两种都有坑,第一种方式会影响控件的点击事件, onTap 方法不会被回调;第二种由于高度在不断改变,会影响 header 内部子Widget的布局,很难做视觉差的控制;
最终方案
最后采用 Stack 来布局,通过 Stack 配合 Positioned ,实现 header 布局在屏幕外,而且可以做到让 content 布局填充父Widget;
PullDragWidget
首先解释一下 Positioned 的基本用法, top 、 bottom 、 height 控制高度和位置,而且两两配合使用, top 和 bottom 可以理解成marginTop和marginBottom, height 顾名思义是直接Widget的高度,如果 top 配置 bottom ,意味着高度等于 parentHeight-top-bottom ,如果 top / bottom 配合 height 使用,高度一般是固定的,当然 top 和 bottom 是接受负数的;
再分析代码,首先**_offsetY 是下拉距离,是一个改变的量初始值为0, content 需要设置 top = _offsetY 和 bottom = -_offsetY**,改变的是上下位置,高度不会改变;同理, header 是采用 top 和 height 控制,高度固定,只需要动态改变 top 即可;
用Flutter写布局真的很简单,我极力推崇使用 Stack 布局,因为它比较灵活,没有太多的限制,用好 Stack 主要还得用好 Positioned ,学好它没错;
卡片控件
卡片实现的效果就是依次层叠,错落有致,这个很容易想到 Stack 来实现,当然有了上面踩坑,用 Stack 算是很轻松了;
重叠的效果使用Stack很简单,错落有致的效果实在起来可能性就比较多了,比如可以使用 Positioned ,也可以包裹 Container 改变 margin 或者 padding ,但是考虑到角度的旋转,我选择使用 Transform ,因为 Transform 不仅可以玩转位移,还有角度和缩放等,其内部实际上是操作一个矩阵变换; Transform 挺好用,但是在 Transform 多层嵌套的某些特殊情况下,会存在不响应 onTap 事件的情况,我想这应该是 Transform 的bug,拖拽事件暂时没有发现问题,这个是不是bug有待确认,暂时不影响使用;
CardStackWidget
_CardWidget
简单总结一下卡片布局代码, CardStackWidget 是管理卡片 Stack 的父控件,负责对每个卡片进行布局, _CardWidget 是对单独卡片内部进行布局,总体来说没有什么难点,细节控制逻辑是在对上层**_CardWidget 和底层 _CardWidget**偏移量的计算;
布局的内容就讲这么多,整体来说还是比较简单,所谓的有些坑也不一定算是坑,只是不适应某些应用场景罢了;
手势识别
Flutter手势识别最常用的是 Listener 和 GestureDetector 这两个Widget,其中 Listener 主要针对原始触摸点进行处理, GestureDetector 已经对原始触摸点加工成了不同的手势;这两个类的方法介绍如下;
Listener
GestureDetector手势回调:
Listener和 GestureDetector 如何抉择,首先 GestureDetector 是基于 Listener 封装,它解决了大部分手势冲突,我们使用 GestureDetector 就够用了,但是 GestureDetector 不是万能的,必要时候需要自定义 RawGestureDetector ;
另外一个很重要的概念,Flutter手势事件是一个从内Widget向外Widget的冒泡机制,假设内外Widget同时监听竖直方向的拖拽事件 onVerticalDragUpdate ,往往都是内层控件获得事件,外层事件被动取消;这样的概念和Android父布局拦截机制就完全不同了;
虽然Flutter没有外层拦截机制,但是似乎还有一线希望,那就是 IgnorePointer 和 AbsorbPointer Widget,这俩哥们可以忽略或者阻止子Widget树不响应Event;
手势分析
基本原理介绍完了,接下来分析案例交互,上面说了我把整体布局拆分成了下拉控件和卡片控件,分析即刻App的拖拽的行为:当下拉控件没有展开下拉菜单时,卡片控件是可以相应上、左、右三个方向的手势,下拉控件只相应一个向下方向的手势;当下拉菜单展开时,卡片不能相应任何手势,下拉控件可以相应竖直方向的所有事件;
上图更加形象解释两种状态下的手势响应,下拉控件是父Widget,卡片控件是子Widget,由于子Widget能优先响手势,所以在初始阶段,我们不能让子Widget响应向下的手势;
由于 GestureDetector 只封装水平和竖直方向的手势,且两种手势不能同时使用,我们从 GestureDetector 源码来看,能不能封装一个监听不同四个方向的手势,;
GestureDetector
GestureDetector最终返回的是 RawGestureDetector ,其中 gestures 是一个 map ,竖直方向的手势在 VerticalDragGestureRecognizer 这个类;
VerticalDragGestureRecognizer
VerticalDragGestureRecognizer继承 DragGestureRecognizer ,大部分逻辑都在 DragGestureRecognizer 中,我们只关注重写的方法:
- _hasSufficientPendingDragDeltaToAccept 方法是关键逻辑,控制是否接受该拖拽手势
- _getDeltaForDetails 返回拖拽进度的dx、dy偏移量
- _getPrimaryValueFromOffset 返回单方向手势value,不同方向(同时拥有水平和竖直)的可以传null
- _isFlingGesture 是否该手势的Fling行为
自定义DragGestureRecognizer
想实现接受三个方向的手势,自定义 DragGestureRecognizer 是一个好的思路;我希望接受上、下、左、右四个方向的参数,根据参数不同监听不同的手势行为,照葫芦画瓢自定义一个接受方向的 GestureRecognizer :
DirectionGestureRecognizer
可参考原Demo
由于 DragGestureRecognizer 的很多方法是私有的,想重新只能copy一份代码出来,然后重写主要的方法,根据不同入参处理不同的手势逻辑;
注意事项
敲黑板了,在自定义 DragGestureRecognizer 时: _getDeltaForDetails 返回值表示 dx 和 dy 的偏移量,在只存在水平或者只存在竖直方向的情况下,需要将另一个方向的 dx 或 dy 置0;
当前Widget树有且只存在一个手势时,手势判断的逻辑**_hasSufficientPendingDragDeltaToAccept 可能不会被调用,这时候一定要重写 _getDeltaForDetails 控制返回 dx 和 dy**;
如何使用
自定义的 DirectionGestureRecognizer 可以配置 left 、 right 、 up 、 down 四个方向的手势,而且支持不同方向的组合;
比如我们只想监听竖直向下方向,就创建**DirectionGestureRecognizer(DirectionGestureRecognizer.down)**的手势识别;
想监听上、左、右的手势,创建**DirectionGestureRecognizer(DirectionGestureRecognizer.left | DirectionGestureRecognizer.right | DirectionGestureRecognizer.up)**的手势识别;
DirectionGestureRecognizer就像一把磨刀石,刀已经磨锋利,砍材就很轻松了,下面进行控件的手势实现;
下拉控件手势
PullDragWidget
PullDragWidget是下拉拖拽控件,根Widget是一个 RawGestureDetector 用来监听手势,其中 gestures 支持向下拖拽和点击两个手势;当下拉控件处于**_opened 状态说 header 已经拉下来,此时配合 IgnorePointer**,禁用子Widget所有的事件监听,自然内部的卡片就相应不了任何事件;
卡片控件手势
同下拉控件一样,卡片控件只需要监听其余三个方向的手势,即可完成任务:
CardStackWidget
手势答疑
- 为什么不用 onPanDown onPanUpdate onPanEnd 来拖动?
这是掘金评论提的问题,我解答一下:在 GestureDetector 中有 Pan 手势和 Drag 手势,这两个手势都能用处拖拽的场景,但不同的是 Drag 手势仅限于 水平 和 竖直 方向的监听, Pan 手势不约束方向任意方向都能监听,除此之外触发条件也不一致, Pan 手势的触发条件是滑动动屏幕的距离 distance 大于 kTouchSlop*2 , Drag 手势的触发条件是 dx 或者 dy 大于 kTouchSlop , dx 、 dy 和 distance 形成勾股定理的三个边长;假设同样在监听竖直滑动这种场景, VerticalDrag 总是比 Pan 先触发;如果下拉控件用 VerticalDrag 卡片控件用 Pan ,下拉控件会优先获取向上的拖拽,卡片控件就会失去向上拖拽的机会,这就实现不了交互了,退一步即使 Pan 的触发条件跟 VerticalDrag 一样,由于Flutter的事件传递是从内到外的,这会导致外层下拉控件完全失去响应机会。以上我的个人理解,如有误导还请大佬评论指正。
手势小结
分析Flutter手势冒泡的特性,父Widget既没有响应事件的优先权,也没有监听单独方向( left 、 right 、 up 、 down )的手势,只能自己想办法自定义 GestureRecognizer ,把原本 Vertical 和 Horizontal 两个方向的手势识别扩展成 left 、 right 、 up 、 down 四个方向,区分开会产生冲突的手势;
当然也可能有其他的方案来实现该交互的手势识别,条条大路通罗马,我只是抛砖引玉,大家有好的方案可以积极留言提出宝贵意见;
总结
知识点
由于篇幅有限并没有介绍完该交互的所有内容,深表遗憾,总结归纳一下代码中用到的知识点:
- Column 、 Row 、 Expanded 、 Stack 、 Positioned 、 Transform 等Widget的使用;
- GestureDetector 、 RawGestureDetector 、 IgnorePointer 等Widget的使用;
- 自定义 GestureRecognizer 实现自定义手势识别;
- AnimationController 、 Tween 等动画的使用;
- EventBus 的使用;
最后
上面章节主要介绍在当前场景下用Flutter布局和手势的实战技巧,其中更深层次手势竞技和分发的源码级分析,有机会再做深入学习和分享;
另外本篇并不是循序渐进的零基础入门,对刚接触的同学可能感觉有点懵,但是没有关系,建议你 clone 一份代码跑起来效果,没准就能提起自己学习的兴趣;
最最后,本篇所有代码都是开源的,你的点赞是对我最大的鼓励。
项目地址: github.com/HitenDev/Fl…
以上所述就是小编给大家介绍的《5G到来,App的未来,是JavaScript,Flutter还是Native ?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机程序设计艺术(第1卷)
[美] Donald E. Knuth / 清华大学出版社 / 2002-9 / 80.00元
第1卷首先介绍编程的基本概念和技术,然后详细讲解信息结构方面的内容,包括信息在计算机内部的表示方法、数据元素之间的结构关系,以及有效的信息处理方法。此外,书中还描述了编程在模拟、数值方法、符号计算、软件与系统设计等方面的初级应用。此第3版增加了数十项简单但重要的算法和技术,并根据当前研究发展趋势在数学预备知识方面做了大量修改。一起来看看 《计算机程序设计艺术(第1卷)》 这本书的介绍吧!