5G到来,App的未来,是JavaScript,Flutter还是Native ?
栏目: JavaScript · 发布时间: 7年前
内容简介: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 ?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ant Colony Optimization
Marco Dorigo、Thomas Stützle / A Bradford Book / 2004-6-4 / USD 45.00
The complex social behaviors of ants have been much studied by science, and computer scientists are now finding that these behavior patterns can provide models for solving difficult combinatorial opti......一起来看看 《Ant Colony Optimization》 这本书的介绍吧!