摘要:前言最近比較熱門,但是成體系的文章并不多,前期避免不了踩坑我這篇文章主要介紹如何使用實(shí)現(xiàn)一個(gè)比較復(fù)雜的手勢(shì)交互,順便分享一下我在使用過程中遇到的一些小坑,減少大家入坑作者鏈接先睹為快本項(xiàng)目支持運(yùn)行,效果如下對(duì)了,順便分享一下生成的小竅門,建
前言
Flutter最近比較熱門,但是Flutter成體系的文章并不多,前期避免不了踩坑;我這篇文章主要介紹如何使用Flutter實(shí)現(xiàn)一個(gè)比較復(fù)雜的手勢(shì)交互,順便分享一下我在使用Flutter過程中遇到的一些小坑,減少大家入坑;
作者:HitenDev先睹為快
鏈接:https://www.jianshu.com/p/4d1...
本項(xiàng)目支持ios&android運(yùn)行,效果如下
對(duì)了,順便分享一下生成gif的小竅門,建議用手機(jī)自帶錄屏功能導(dǎo)出mp4文件到電腦,然后電腦端用ffmpeg命令行處理,控制gif的質(zhì)量和文件大小,我的建議是分辨率控制在270p,幀率在10左右;
交互分析看文章的小伙伴最好能手持即刻App),親自體驗(yàn)一下探索頁(yè)的交互,是黃色Logo黃色主題色的即刻;有人稱‘黃即’;
即刻App原版功能有卡片旋轉(zhuǎn),卡片撤回和卡片自動(dòng)移除,時(shí)間關(guān)系暫時(shí)沒有實(shí)現(xiàn),但核心的功能都在;
從一個(gè)Android開發(fā)者的習(xí)慣來看待,這個(gè)交互可拆分內(nèi)外兩層控件,外層我們需要一個(gè)整體下拉的控件,我稱為下拉控件;內(nèi)層我們需要實(shí)現(xiàn)一個(gè)上、下、左、右四方向拖拽移動(dòng)的控件,我們稱為卡片控件;下拉控件和卡片控件不僅要處理手勢(shì),還需要處理子Widget的布局;下面我再分析細(xì)節(jié)功能:
下拉控件:
子控件從上到下豎直擺放,頂部菜單默認(rèn)隱藏在屏幕外
下拉手勢(shì)所有子控件下移,菜單視覺差效果
支持點(diǎn)擊自動(dòng)展開、收起效果
卡片控件
卡片層疊布局,錯(cuò)落有致
最上層卡片支持手勢(shì)拖拽
其他卡片相應(yīng)拖拽小幅位移
松手移除卡片
碼上入手 熱身套用App開發(fā)伎倆,實(shí)現(xiàn)上面的交互無非就是控件布局和手勢(shì)識(shí)別。當(dāng)然Flutter開發(fā)也是這些套路,只不過萬(wàn)物皆是Widget,在Flutter中常用的基本布局有Column、Row、Stack等,手勢(shì)識(shí)別有Listener、GestureDetector、RawGestureDetector等,這是本文重點(diǎn)講解的控件,不限于上面這幾個(gè)Widget,因?yàn)镕lutter提供的Widget太多了,重點(diǎn)的控件需要牢記外,其他時(shí)候真是現(xiàn)用現(xiàn)查;
所以下面我們從布局和手勢(shì)這兩個(gè)大的技術(shù)點(diǎn),來一一擊破功能點(diǎn);
布局?jǐn)[放這里所謂的布局,包括Widget的尺寸大小和位置的控制,一般都是父Widget掌管子Widget的命運(yùn),F(xiàn)lutter就是一層一層Widget嵌套,不要擔(dān)心,下面從外到內(nèi)具體案例講解;
首先我們要實(shí)現(xiàn)最外層布局,效果是:子Widget豎直擺放,且最上面的Widget默認(rèn)需要擺放在屏幕外;
如上圖所示,紅色區(qū)域是屏幕范圍,header是頭部隱藏的菜單布局,content是卡片布局的主體;
先說入的坑
豎直布局我最先想到的是Column,我想要的效果是content高度和父Widget的高度一致,我首先想到是讓Expanded包裹content,結(jié)果是content的高度永遠(yuǎn)等于Column高度減header高度,造成現(xiàn)象就是content高度不填充,或者是擠壓現(xiàn)象,如果繼續(xù)使用Colunm可能就得放棄Expanded,手動(dòng)給content賦值高度,沒準(zhǔn)是個(gè)辦法,但我不愿意手動(dòng)賦值content的高度,太不優(yōu)雅了,最后果斷棄用Column;
另一個(gè)問題是如何隱藏header,我想到兩種方案:
采用外層Transform包裹整個(gè)布局,內(nèi)層Transform包裹header,然后賦值內(nèi)層dy = -headerHeight,隨著手勢(shì)下拉動(dòng)態(tài),并不改變header的Transform,而是改變最外層Transform的dy;
動(dòng)態(tài)改變header高度,初始高度為0,隨著手勢(shì)下拉動(dòng)態(tài)計(jì)算;
但是上面這兩種都有坑,第一種方式會(huì)影響控件的點(diǎn)擊事件,onTap方法不會(huì)被回調(diào);第二種由于高度在不斷改變,會(huì)影響header內(nèi)部子Widget的布局,很難做視覺差的控制;
最終方案
最后采用Stack來布局,通過Stack配合Positioned,實(shí)現(xiàn)header布局在屏幕外,而且可以做到讓content布局填充父Widget;
PullDragWidget
首先解釋一下Positioned的基本用法,top、bottom、height控制高度和位置,而且兩兩配合使用,top和bottom可以理解成marginTop和marginBottom,height顧名思義是直接Widget的高度,如果top配置bottom,意味著高度等于parentHeight-top-bottom,如果top/bottom配合height使用,高度一般是固定的,當(dāng)然top和bottom是接受負(fù)數(shù)的;
再分析代碼,首先_offsetY是下拉距離,是一個(gè)改變的量初始值為0,content需要設(shè)置top = _offsetY和bottom = -_offsetY,改變的是上下位置,高度不會(huì)改變;同理,header是采用top和height控制,高度固定,只需要?jiǎng)討B(tài)改變top即可;
用Flutter寫布局真的很簡(jiǎn)單,我極力推崇使用Stack布局,因?yàn)樗容^靈活,沒有太多的限制,用好Stack主要還得用好Positioned,學(xué)好它沒錯(cuò);
卡片實(shí)現(xiàn)的效果就是依次層疊,錯(cuò)落有致,這個(gè)很容易想到Stack來實(shí)現(xiàn),當(dāng)然有了上面踩坑,用Stack算是很輕松了;
重疊的效果使用Stack很簡(jiǎn)單,錯(cuò)落有致的效果實(shí)在起來可能性就比較多了,比如可以使用Positioned,也可以包裹Container改變margin或者padding,但是考慮到角度的旋轉(zhuǎn),我選擇使用Transform,因?yàn)?strong>Transform不僅可以玩轉(zhuǎn)位移,還有角度和縮放等,其內(nèi)部實(shí)際上是操作一個(gè)矩陣變換;Transform挺好用,但是在Transform多層嵌套的某些特殊情況下,會(huì)存在不響應(yīng)onTap事件的情況,我想這應(yīng)該是Transform的bug,拖拽事件暫時(shí)沒有發(fā)現(xiàn)問題,這個(gè)是不是bug有待確認(rèn),暫時(shí)不影響使用;
CardStackWidget
_CardWidget
簡(jiǎn)單總結(jié)一下卡片布局代碼,CardStackWidget是管理卡片Stack的父控件,負(fù)責(zé)對(duì)每個(gè)卡片進(jìn)行布局,_CardWidget是對(duì)多帶帶卡片內(nèi)部進(jìn)行布局,總體來說沒有什么難點(diǎn),細(xì)節(jié)控制邏輯是在對(duì)上層_CardWidget和底層_CardWidget偏移量的計(jì)算;
布局的內(nèi)容就講這么多,整體來說還是比較簡(jiǎn)單,所謂的有些坑也不一定算是坑,只是不適應(yīng)某些應(yīng)用場(chǎng)景罷了;
手勢(shì)識(shí)別Flutter手勢(shì)識(shí)別最常用的是Listener和GestureDetector這兩個(gè)Widget,其中Listener主要針對(duì)原始觸摸點(diǎn)進(jìn)行處理,GestureDetector已經(jīng)對(duì)原始觸摸點(diǎn)加工成了不同的手勢(shì);這兩個(gè)類的方法介紹如下;
Listener
GestureDetector手勢(shì)回調(diào):
Listener和GestureDetector如何抉擇,首先GestureDetector是基于Listener封裝,它解決了大部分手勢(shì)沖突,我們使用GestureDetector就夠用了,但是GestureDetector不是萬(wàn)能的,必要時(shí)候需要自定義RawGestureDetector;
另外一個(gè)很重要的概念,F(xiàn)lutter手勢(shì)事件是一個(gè)從內(nèi)Widget向外Widget的冒泡機(jī)制,假設(shè)內(nèi)外Widget同時(shí)監(jiān)聽豎直方向的拖拽事件onVerticalDragUpdate,往往都是內(nèi)層控件獲得事件,外層事件被動(dòng)取消;這樣的概念和Android父布局?jǐn)r截機(jī)制就完全不同了;
雖然Flutter沒有外層攔截機(jī)制,但是似乎還有一線希望,那就是IgnorePointer和AbsorbPointerWidget,這倆哥們可以忽略或者阻止子Widget樹不響應(yīng)Event;
基本原理介紹完了,接下來分析案例交互,上面說了我把整體布局拆分成了下拉控件和卡片控件,分析即刻App的拖拽的行為:當(dāng)下拉控件沒有展開下拉菜單時(shí),卡片控件是可以相應(yīng)上、左、右三個(gè)方向的手勢(shì),下拉控件只相應(yīng)一個(gè)向下方向的手勢(shì);當(dāng)下拉菜單展開時(shí),卡片不能相應(yīng)任何手勢(shì),下拉控件可以相應(yīng)豎直方向的所有事件;
上圖更加形象解釋兩種狀態(tài)下的手勢(shì)響應(yīng),下拉控件是父Widget,卡片控件是子Widget,由于子Widget能優(yōu)先響手勢(shì),所以在初始階段,我們不能讓子Widget響應(yīng)向下的手勢(shì);
由于GestureDetector只封裝水平和豎直方向的手勢(shì),且兩種手勢(shì)不能同時(shí)使用,我們從GestureDetector源碼來看,能不能封裝一個(gè)監(jiān)聽不同四個(gè)方向的手勢(shì),;
GestureDetector
GestureDetector最終返回的是RawGestureDetector,其中gestures是一個(gè)map,豎直方向的手勢(shì)在VerticalDragGestureRecognizer這個(gè)類;
VerticalDragGestureRecognizer
VerticalDragGestureRecognizer繼承DragGestureRecognizer,大部分邏輯都在DragGestureRecognizer中,我們只關(guān)注重寫的方法:
_hasSufficientPendingDragDeltaToAccept方法是關(guān)鍵邏輯,控制是否接受該拖拽手勢(shì)
_getDeltaForDetails返回拖拽進(jìn)度的dx、dy偏移量
_getPrimaryValueFromOffset返回單方向手勢(shì)value,不同方向(同時(shí)擁有水平和豎直)的可以傳null
_isFlingGesture是否該手勢(shì)的Fling行為
想實(shí)現(xiàn)接受三個(gè)方向的手勢(shì),自定義DragGestureRecognizer是一個(gè)好的思路;我希望接受上、下、左、右四個(gè)方向的參數(shù),根據(jù)參數(shù)不同監(jiān)聽不同的手勢(shì)行為,照葫蘆畫瓢自定義一個(gè)接受方向的GestureRecognizer:
DirectionGestureRecognizer
可參考原Demo
由于DragGestureRecognizer的很多方法是私有的,想重新只能copy一份代碼出來,然后重寫主要的方法,根據(jù)不同入?yún)⑻幚聿煌氖謩?shì)邏輯;
注意事項(xiàng)
敲黑板了,在自定義DragGestureRecognizer時(shí):_getDeltaForDetails返回值表示dx和dy的偏移量,在只存在水平或者只存在豎直方向的情況下,需要將另一個(gè)方向的dx或dy置0;
當(dāng)前Widget樹有且只存在一個(gè)手勢(shì)時(shí),手勢(shì)判斷的邏輯_hasSufficientPendingDragDeltaToAccept可能不會(huì)被調(diào)用,這時(shí)候一定要重寫_getDeltaForDetails控制返回dx和dy;
如何使用
自定義的DirectionGestureRecognizer可以配置left、right、up、down四個(gè)方向的手勢(shì),而且支持不同方向的組合;
比如我們只想監(jiān)聽豎直向下方向,就創(chuàng)建DirectionGestureRecognizer(DirectionGestureRecognizer.down)的手勢(shì)識(shí)別;
想監(jiān)聽上、左、右的手勢(shì),創(chuàng)建DirectionGestureRecognizer(DirectionGestureRecognizer.left | DirectionGestureRecognizer.right | DirectionGestureRecognizer.up)的手勢(shì)識(shí)別;
DirectionGestureRecognizer就像一把磨刀石,刀已經(jīng)磨鋒利,砍材就很輕松了,下面進(jìn)行控件的手勢(shì)實(shí)現(xiàn);
PullDragWidget
PullDragWidget是下拉拖拽控件,根Widget是一個(gè)RawGestureDetector用來監(jiān)聽手勢(shì),其中gestures支持向下拖拽和點(diǎn)擊兩個(gè)手勢(shì);當(dāng)下拉控件處于_opened狀態(tài)說header已經(jīng)拉下來,此時(shí)配合IgnorePointer,禁用子Widget所有的事件監(jiān)聽,自然內(nèi)部的卡片就相應(yīng)不了任何事件;
同下拉控件一樣,卡片控件只需要監(jiān)聽其余三個(gè)方向的手勢(shì),即可完成任務(wù):
CardStackWidget
為什么不用 onPanDown onPanUpdate onPanEnd 來拖動(dòng)?
這是掘金評(píng)論提的問題,我解答一下:在GestureDetector中有Pan手勢(shì)和Drag手勢(shì),這兩個(gè)手勢(shì)都能用處拖拽的場(chǎng)景,但不同的是Drag手勢(shì)僅限于水平和豎直方向的監(jiān)聽,Pan手勢(shì)不約束方向任意方向都能監(jiān)聽,除此之外觸發(fā)條件也不一致,Pan手勢(shì)的觸發(fā)條件是滑動(dòng)動(dòng)屏幕的距離distance大于kTouchSlop*2,Drag手勢(shì)的觸發(fā)條件是dx或者dy大于kTouchSlop,dx、dy和distance形成勾股定理的三個(gè)邊長(zhǎng);假設(shè)同樣在監(jiān)聽豎直滑動(dòng)這種場(chǎng)景,VerticalDrag總是比Pan先觸發(fā);如果下拉控件用VerticalDrag卡片控件用Pan,下拉控件會(huì)優(yōu)先獲取向上的拖拽,卡片控件就會(huì)失去向上拖拽的機(jī)會(huì),這就實(shí)現(xiàn)不了交互了,退一步即使Pan的觸發(fā)條件跟VerticalDrag一樣,由于Flutter的事件傳遞是從內(nèi)到外的,這會(huì)導(dǎo)致外層下拉控件完全失去響應(yīng)機(jī)會(huì)。以上我的個(gè)人理解,如有誤導(dǎo)還請(qǐng)大佬評(píng)論指正。
分析Flutter手勢(shì)冒泡的特性,父Widget既沒有響應(yīng)事件的優(yōu)先權(quán),也沒有監(jiān)聽多帶帶方向(left、right 、up 、down)的手勢(shì),只能自己想辦法自定義GestureRecognizer,把原本Vertical和Horizontal兩個(gè)方向的手勢(shì)識(shí)別擴(kuò)展成left、right 、up 、down四個(gè)方向,區(qū)分開會(huì)產(chǎn)生沖突的手勢(shì);
當(dāng)然也可能有其他的方案來實(shí)現(xiàn)該交互的手勢(shì)識(shí)別,條條大路通羅馬,我只是拋磚引玉,大家有好的方案可以積極留言提出寶貴意見;
總結(jié)知識(shí)點(diǎn)
由于篇幅有限并沒有介紹完該交互的所有內(nèi)容,深表遺憾,總結(jié)歸納一下代碼中用到的知識(shí)點(diǎn):
Column、Row、Expanded、Stack、Positioned、Transform等Widget的使用;
GestureDetector、RawGestureDetector、IgnorePointer等Widget的使用;
自定義GestureRecognizer實(shí)現(xiàn)自定義手勢(shì)識(shí)別;
AnimationController、Tween等動(dòng)畫的使用;
EventBus的使用;
最后上面章節(jié)主要介紹在當(dāng)前場(chǎng)景下用Flutter布局和手勢(shì)的實(shí)戰(zhàn)技巧,其中更深層次手勢(shì)競(jìng)技和分發(fā)的源碼級(jí)分析,有機(jī)會(huì)再做深入學(xué)習(xí)和分享;
另外本篇并不是循序漸進(jìn)的零基礎(chǔ)入門,對(duì)剛接觸的同學(xué)可能感覺有點(diǎn)懵,但是沒有關(guān)系,建議你clone一份代碼跑起來效果,沒準(zhǔn)就能提起自己學(xué)習(xí)的興趣;
最最后,本篇所有代碼都是開源的,你的點(diǎn)贊是對(duì)我最大的鼓勵(lì)。
項(xiàng)目地址:閱讀更多
https://github.com/HitenDev/F...
一波Flutter酷炫特效來襲
金三銀四,2019最新面試實(shí)戰(zhàn)總結(jié)
從來不糾結(jié)算法,冒泡排序這樣優(yōu)化?
動(dòng)畫:一招學(xué)會(huì)TCP的三次握手和四次揮手
關(guān)于Gradle, 搞定Groovy閉包這一篇就夠了
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/54982.html
摘要:前言最近比較熱門,但是成體系的文章并不多,前期避免不了踩坑我這篇文章主要介紹如何使用實(shí)現(xiàn)一個(gè)比較復(fù)雜的手勢(shì)交互,順便分享一下我在使用過程中遇到的一些小坑,減少大家入坑作者鏈接先睹為快本項(xiàng)目支持運(yùn)行,效果如下對(duì)了,順便分享一下生成的小竅門,建 前言 Flutter最近比較熱門,但是Flutter成體系的文章并不多,前期避免不了踩坑;我這篇文章主要介紹如何使用Flutter實(shí)現(xiàn)一個(gè)比較復(fù)雜...
摘要:我們都知道,最近,發(fā)布了月份編程語(yǔ)言排行榜。其中,前三名依然健穩(wěn)不變,他們分別是,第四則為,第五則為。這樣比較一下,發(fā)現(xiàn)確實(shí)會(huì)長(zhǎng)期是編程語(yǔ)言的第一名。很多人看好的語(yǔ)言,卻仍在左右徘徊。 我們都知道,最近,TIOBE 發(fā)布了 5 月份編程語(yǔ)言排行榜。其中,前三名依然健穩(wěn)不變,他們分別是 Java、C、C++,第四則為: Python ,第五則為 VB .NET。 下面兩張圖,我們可以看到...
摘要:所以就找外包公司,找到一個(gè)有經(jīng)驗(yàn)的程序員來做,這樣做既可以保證質(zhì)量,有可以跟上進(jìn)度。為什么不建議去外包為什么不要去外包公司總體原因如下沒有歸屬感想想工作周圍大部分不是自己的同事,想找人說個(gè)話都難。 前言 最近有好多人討論外包,前幾天看到一個(gè)帖子說就是有一個(gè)外包吃了公司的的零食,遭到HR當(dāng)場(chǎng)批評(píng), 搞的整個(gè)IT界備受關(guān)注,那么外包公司和非外包公司有什么樣的不一樣呢?我今天也說說我的看法! ...
閱讀 2231·2021-11-22 09:34
閱讀 1342·2021-10-11 10:59
閱讀 4442·2021-09-22 15:56
閱讀 3297·2021-09-22 15:08
閱讀 3411·2019-08-30 14:01
閱讀 782·2019-08-30 11:16
閱讀 1136·2019-08-26 13:51
閱讀 2915·2019-08-26 13:43