ConstraintLayout2.0一篇寫不完之Stagger交錯(cuò)

點(diǎn)擊上方藍(lán)字關(guān)注我,知識(shí)會(huì)給你力量

在Flutter中,有個(gè)StaggerAnimation,可以實(shí)現(xiàn)交錯(cuò)動(dòng)畫效果,這個(gè)效果相當(dāng)于在申明式編程中為多個(gè)動(dòng)畫增加了多條時(shí)間線,在Android中,以往要實(shí)現(xiàn)交錯(cuò)動(dòng)畫效果,需要為每個(gè)屬性動(dòng)畫設(shè)置Delay時(shí)間,或者監(jiān)聽其生命周期,而在MotionLayout中,可以直接在xml中設(shè)置交錯(cuò)動(dòng)畫的驅(qū)動(dòng)流程,極大的簡(jiǎn)化了動(dòng)畫的創(chuàng)建。
在MotionLayout中,它為每個(gè)被標(biāo)記了motionStagger的View分配了一個(gè)float value(沒有標(biāo)記的View不會(huì)被引入交錯(cuò)動(dòng)畫),float value最小的(V0)的View首先被啟動(dòng)。float value最高的(Vn)的View最后啟動(dòng)。
這是motionStagger的動(dòng)畫總綱,但是具體的啟動(dòng)時(shí)間和執(zhí)行順序,由下面的這些參數(shù)來精確控制。
MotionLayout中每個(gè)VIew的motionStagger value被標(biāo)記為S(Vi) 總的Stagger Value被標(biāo)記為stagger,取值為0.0-1.0,通常在Transition中指定 在Transition中指定動(dòng)畫的duration
在這基礎(chǔ)上,motionStagger定義了下面兩條規(guī)則:
View的動(dòng)畫持續(xù)時(shí)長(zhǎng) = duration * (1 - stagger) View的動(dòng)畫啟動(dòng)時(shí)間點(diǎn)算法為:duration * (stagger - stagger * (S(Vi) - S(V0)) / (S(Vn) - S(V0)))
其中最奇怪的就是這個(gè)參數(shù):(S(Vi) - S(V0)) / (S(Vn) - S(V0))
我們可以梳理下,我們給MotionLayout中的所有需要做StaggerAnimation的View標(biāo)記了motionStagger value,這些元素形成了一個(gè)數(shù)組,從大到小進(jìn)行排序:
【Sv0—Sv1—Sv2—Sv3—Sv4—Sv5】
在這個(gè)數(shù)組只,(S(Vi) - S(V0)) / (S(Vn) - S(V0))這個(gè)表達(dá)式所具有的幾何含義是什么呢?實(shí)際上就是點(diǎn)陣的Manhattan distance(曼哈頓距離),具體如下所示。

好了,看到這里還不走的朋友,心里也忍不住會(huì)想了,這TM是什么鬼?我寫個(gè)動(dòng)畫還TM要這些??
是的,有的復(fù)雜,我們先舉個(gè)例子來看下。
首先,我假定設(shè)置MotionLayout中有3個(gè)View——View1、View2、View3,分別設(shè)置motionStagger value為7,5,3,再給Transition設(shè)置staggered為0.6,duration為5000,這些都是我假設(shè)的,我們來看下這個(gè)狀態(tài)下,MotionLayout的StaggerAnimation是如何創(chuàng)建的。
首先,計(jì)算duration。
View的動(dòng)畫持續(xù)時(shí)長(zhǎng) = duration * (1 - stagger)
即:View的動(dòng)畫持續(xù)時(shí)長(zhǎng) = 5000 * (1 - 0.6) = 2000
接下來,針對(duì)每個(gè)View計(jì)算,所以,Sv1= 7,Sv2= 5,Sv3= 3。
duration * (stagger - stagger * (S(Vi) - S(V0)) / (S(Vn) - S(V0)))
即:
View1:?jiǎn)?dòng)于 5000 * (0.6 - 0.6 * (7 - 3)/(7 - 3)) = 0
終止于 0 + 2000 = 2000
View2:?jiǎn)?dòng)于 5000 * (0.6 - 0.6 * (5 - 3)/(7 - 3)) = 1500
終止于 1500 + 2000 = 3500
View3:?jiǎn)?dòng)于 5000 * (0.6 - 0.6 * (3 - 3)/(7 - 3)) = 3000
終止于 3000 + 2000 = 5000
是不是有點(diǎn)意思。
我感覺沒意思,難道我寫動(dòng)畫還要這樣湊數(shù)字嗎?
如果我們要從正向來理解這些公式的含義,那么要將上面的公式進(jìn)行一下變形處理。
startPoint = duration * (stagger - stagger * (S(Vi) - S(V0)) / (S(Vn) - S(V0)))
再理清楚一點(diǎn):
animationStartTime = totalDuration * (stagger - stagger * ((staggerCurrentView - lowestStaggerValue)/(highestStaggerValue - lowestStaggerValue))
totalDuration好說,剩下的就是stagger值的確定了。
我們同樣用之前那個(gè)例子,我假定設(shè)置MotionLayout中有3個(gè)View——View1、View2、View3,三個(gè)View依次出現(xiàn)。
前面我們的公式指出了:viewDuration = totalDuration * (1 - stagger),那么將其變形處理后,我們就得到了stagger:
stagger = 1 - (viewDuration / totalDuration)
所以,如果我要三個(gè)動(dòng)畫依次出現(xiàn),那么viewDuration / totalDuration就是1/3,也就是說stagger約為0.6。
確定了stagger之后,我們?cè)賮砜磀uration,由于stagger確定,所以totalDuration可以自由設(shè)置,viewDuration會(huì)根據(jù)其它兩個(gè)參數(shù)動(dòng)態(tài)變化。
那么每個(gè)View的motionStagger呢?實(shí)際上在開發(fā)動(dòng)畫的時(shí)候,通常都是先使用遞減數(shù)列或者遞增數(shù)列來做(取決于你的視圖展示順序),再根據(jù)動(dòng)畫參數(shù)進(jìn)行微調(diào),例如前面的例子,我們可以給View1、2、3分別設(shè)置motionStagger為3、2、1,那么啟動(dòng)順序如下所示。
totalDuration = 5000,stagger = 0.6,viewDuration = 2000
即:
View1:?jiǎn)?dòng)于 5000 * (0.6 - 0.6 * (3 - 1)/(3 - 1)) = 0
終止于 0 + 2000 = 2000
View2:?jiǎn)?dòng)于 5000 * (0.6 - 0.6 * (2 - 1)/(3 - 1)) = 1500
終止于 1500 + 2000 = 3500
View3:?jiǎn)?dòng)于 5000 * (0.6 - 0.6 * (1 - 1)/(3 - 1)) = 3000
終止于 3000 + 2000 = 5000
有沒有發(fā)現(xiàn),和前面的結(jié)果是一致的!
越來越難了不是嗎,寫個(gè)動(dòng)畫還得用這么多數(shù)學(xué)公式!
其實(shí)不用。
最后我們來總結(jié)下,一句話讓你了解Stagger。
?當(dāng)MotionLayout中的所有View的motionStagger value遞增或者遞減時(shí),在Transition中設(shè)置的staggered控制的就是每個(gè)View啟動(dòng)的時(shí)間間隔,staggered value越小,間隔越短,極端下,為0時(shí),沒有Stagger效果,為1時(shí),每個(gè)View動(dòng)畫完成后才執(zhí)行下一個(gè)。
?
以上。
當(dāng)然,你不懂這些公式也能寫,但是懂了能讓你寫的更清晰,這就是「湊」和「算」的區(qū)別。
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點(diǎn)擊原文一鍵直達(dá)
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
更文不易,點(diǎn)個(gè)“三連”支持一下??
