ConstraintLayout2.0一篇寫不完之KeyCycles的秘密

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

KeyCycle與KeyFrame類似,但是又比KeyFrame復(fù)雜,復(fù)雜在于KeyFrame只是單幀,而KeyCycle則是在KeyFrame的基礎(chǔ)上,增加了周期性的處理,所以,KeyCycle的核心就是周期,KeyCycle決定了在Scene中所有需要重復(fù)處理的部分操作,它的核心API如下所示。
framePosition:作為一個(gè)KeyFrame,KeyCycle必須知道在場(chǎng)景的哪一點(diǎn)上進(jìn)行操作 motionTarget:指定的View ID wavePeriod:周期數(shù)量 waveOffset:起始位置的偏移 waveShape:Cycle的波形

MotionLayout提供了CycleEditor來(lái)幫助開發(fā)者編輯KeyCycle,下載地址如下:
https://github.com/googlesamples/android-ConstraintLayoutExamples/releases/download/1.0/CycleEditor.jar
直接執(zhí)行即可,點(diǎn)擊file中的parse,就可以將編輯區(qū)域的xml轉(zhuǎn)換為波形。
java -jar CycleEditor.jar
分割Scene
在創(chuàng)建KeyCycle之前的第一件事,就是通過(guò)使用不同的framePositions把你的Scene分成多部分組合的Partial Scene,接下來(lái)就可以通過(guò)使用wavePeriod來(lái)指定你想要的每個(gè)部分的周期數(shù),以及waveShape來(lái)指定具體的波形。

wavePeriod是KeyCycle最難理解的一部分,要掌握wavePeriod的定義,就必須先了解Partial Scene,Partial Scene指的是當(dāng)前指定點(diǎn)的前一個(gè)點(diǎn)和后一個(gè)點(diǎn),總共三個(gè)點(diǎn)構(gòu)成的區(qū)域,這點(diǎn)非常重要。
在某個(gè)framePosition的KeyCycle中指定wavePeriod,其實(shí)就是指在這個(gè)Partial Scene中,有幾個(gè)周期的波形來(lái)填滿這個(gè)區(qū)域。
但是這里問(wèn)題又來(lái)了,每個(gè)framePosition都被周圍的framePosition有關(guān),那么wavePeriod不是被重復(fù)計(jì)算了嗎?
沒錯(cuò)。。。所以在整個(gè)Partial Scene中的wavePeriod是由Partial Scene中所有framePosition的的wavePeriod之和確定的。很繞是不是,是就對(duì)了。
這也是為什么KeyCycle有個(gè)單獨(dú)的生成工具的原因,結(jié)合KeyCycleEditor,還是比較能理解的。
wavePeriod已經(jīng)很繞了,但是繞的還在后面。
我們?cè)賮?lái)看看KeyCycle中指定的具體屬性值的含義。
例如,我們?cè)贙eyCycle中指定rotation為20,代碼如下所示。
<KeyCycle
motion:framePosition="0"
motion:target="@+id/button"
motion:wavePeriod="0"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="20"/>
這個(gè)rotation為20是什么意思?你以為是當(dāng)然framePosition的屬性值為20嗎?太年輕了。。。
其實(shí)這個(gè)屬性值與View在當(dāng)前framePosition的屬性值,并沒有直接聯(lián)系。。。
是不是很奇怪,的確如此,那么這玩意兒到底是干嘛的呢???
這里我們需要轉(zhuǎn)換一下思路,那就是KeyCycle里面設(shè)置的一切東西,都是為了畫出「波形圖」,所以,這些參數(shù)的設(shè)置,就是為了修改波形圖的具體形狀。
<KeyFrameSet>
<KeyCycle
motion:framePosition="0"
motion:target="@+id/button"
motion:wavePeriod="0"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="0"/>
<KeyCycle
motion:framePosition="50"
motion:target="@+id/button"
motion:wavePeriod="1"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="10"/>
<KeyCycle
motion:framePosition="100"
motion:target="@+id/button"
motion:wavePeriod="0"
motion:waveOffset="0"
motion:waveShape="sin"
android:rotation="30"/>
</KeyFrameSet>
這樣一個(gè)KeyCycle最后形成的波形圖就是這樣。

由此可以發(fā)現(xiàn),每個(gè)framePosition的屬性值,就是為了畫出波形圖的波峰。
在這個(gè)的基礎(chǔ)上,waveOffset就好理解了,它的作用就是給framePosition的當(dāng)前value增加一個(gè)初始值,這個(gè)初始值同樣是為了修改波形。
要干嘛
你說(shuō)KeyCycle這玩意兒整這么復(fù)雜,到底有什么用呢??
我們有了KeyFrame,可以用來(lái)添加中間態(tài)關(guān)鍵幀,那么還要KeyCycle干嘛呢?
說(shuō)到這來(lái),就不能不提下Monotonic Spline(單調(diào)采樣)了,通常的關(guān)鍵幀插值算法都是使用的單調(diào)采樣,但是這樣無(wú)法做到曲線的圓滑過(guò)渡,就像下圖中的綠色曲線,這樣四個(gè)點(diǎn)使用單調(diào)采樣,就變成了下面這樣的曲線,過(guò)渡會(huì)非常生硬。

那么為了讓曲線圓滑過(guò)渡,KeyCycle使用的是Typical Spline(特征采樣),就如上圖中的紫色曲線,四個(gè)點(diǎn)被圓滑的連接了起來(lái)。
如果僅僅是為了讓曲線能圓滑過(guò)渡,那么你就太小看KeyCycle了,不得不說(shuō)老外做的這些東西,總能在一些你覺得無(wú)關(guān)緊要的地方,做的非常深入。
KeyCycle的核心在于波形,而波是什么呢?

上面這張圖表達(dá)了sin和cos的幾何含義,也是sin和cos的來(lái)源。
說(shuō)句不像傅里葉變換的話,我們可以將一個(gè)View的曲線運(yùn)動(dòng),拆解成多個(gè)不同波形運(yùn)動(dòng)的疊加。
例如我們對(duì)一個(gè)View的translationX同時(shí)設(shè)置sin和cos的KeyCycle,最終形成的運(yùn)動(dòng)軌跡,就是一個(gè)圓形!
所以,由此及彼,我們可以復(fù)合多個(gè)屬性的同時(shí),通過(guò)不同的波形疊加,實(shí)現(xiàn)任何你想要的運(yùn)動(dòng)軌跡!這TM就牛逼了啊,簡(jiǎn)直就是傅里葉變換在Android動(dòng)畫中的實(shí)現(xiàn)了。
在CycleEditor中,有一些自帶的Demo,可以讓你充分的了解這個(gè)思想,例如下面這個(gè)例子。

太復(fù)雜了是嗎?
CustomWave shape in keyCycle
CL2.1之后,motion:waveShape除了之前定義的sin、cos、bounce這些預(yù)設(shè)曲線外,你還可以設(shè)置自定義的波形曲線,定義方式如下所示。
<KeyCycle motion:waveShape=”spline(0.0, 1.0, -1.0, 0)” />
這就有點(diǎn)牛逼了,本來(lái)就很復(fù)雜了,這下還來(lái)了自定義曲線,再見。
KeyCycle確實(shí)比較強(qiáng)大,但是也非常復(fù)雜,強(qiáng)烈建議大家使用CycleEditor來(lái)學(xué)習(xí),KeyCycle這種東西,就像核武器一樣,可以不用,但是不能沒有。
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點(diǎn)擊原文一鍵直達(dá)
專注 Android-Kotlin-Flutter 歡迎大家訪問(wèn)
往期推薦
更文不易,點(diǎn)個(gè)“三連”支持一下??
