?感謝大家對(duì)前兩篇的支持,在第一篇的評(píng)論中,簡(jiǎn)友YouXianMing提出了更好的實(shí)現(xiàn)思路,同樣在評(píng)論中,原動(dòng)效設(shè)計(jì)者 moonjoin親自現(xiàn)身捧場(chǎng),非常感謝!?這也說(shuō)明以分享來(lái)拋磚引玉是有效果的。
慣例,為了讓第一次來(lái)的同學(xué)對(duì)本系列有所了解,我先貼一下?完整的效果圖,有興趣的同學(xué),請(qǐng)移步本系列第一篇,可以的話請(qǐng)多提提建議,非常感謝,以下是效果圖
不多說(shuō)了,我們開(kāi)始第三篇。
前兩篇?聊到了一些技術(shù),而這一篇我們則側(cè)重于思路,一起來(lái)聊一下階段3和階段4。
那么,我們先看一下階段3的效果圖,慣例,前幾個(gè)階段的動(dòng)畫我們用灰色 正常速度表示,當(dāng)前階段則使用彩色慢速,如圖
有的同學(xué)要說(shuō)了,這個(gè)太簡(jiǎn)單了,就是一平移嘛。
沒(méi)錯(cuò),就是平移。可能和我一樣,很多同學(xué)最早接觸的動(dòng)畫就是平移、旋轉(zhuǎn)和縮放,?可以算基本功了。
所以階段3,我們就不多聊了,方案有很多種,比如修改layer的position.y、transform.translation.y都可以(對(duì)此不熟悉的同學(xué)請(qǐng)戳官方文檔中的可動(dòng)畫的屬性和動(dòng)畫支持的key path),甚至可以用本系列第二篇提到的stroke方案。
階段3不是沒(méi)有價(jià)值,因?yàn)閺碾A段3中我們可以得到一個(gè)重要結(jié)論,那就是:我們可以利用經(jīng)驗(yàn)來(lái)解決問(wèn)題。
這?句看上去像是廢話,不要急,我們接下來(lái)要看看,?把分解問(wèn)題和這個(gè)結(jié)論結(jié)合起來(lái),會(huì)是什么效果。
下面是階段4的效果圖,大家請(qǐng)看
?是不是感覺(jué)比前3個(gè)階段復(fù)雜,為什么會(huì)有這種感覺(jué)呢?
一個(gè)重要的原因是,我們覺(jué)察到階段4是多個(gè)動(dòng)畫組合而成的,不像前3個(gè)階段那樣是單個(gè)動(dòng)畫。
?像我一樣單核的同學(xué)估計(jì)要瘋了,恩,這兒在變,?恩?那兒怎么也在變?
所以我們?最好還是把這組動(dòng)畫分解一下,一個(gè)一個(gè)來(lái)處理。
但是怎么分解呢,?一時(shí)可能沒(méi)思路,那我們不如來(lái)描述一下這個(gè)動(dòng)畫吧。
比如這樣:“一個(gè)圓被頭頂上的線砸扁了,線砸進(jìn)圓?里,?線變粗了。”
但仔細(xì)看看,砸進(jìn)去的過(guò)程中,線在圓外的部分是細(xì)的,在圓內(nèi)的部分是粗的,有點(diǎn)復(fù)雜,?不如就以圓為界,分成一細(xì)一粗兩條線吧。
我們?cè)倜枋鲆淮危骸耙粋€(gè)圓被頭頂上的細(xì)線砸扁了,細(xì)線慢慢消失了,圓里面慢慢出現(xiàn)了一條粗線。”
這個(gè)描述里都有什么?
圓漸漸扁了,圓外的細(xì)線漸漸消失了,圓內(nèi)的粗線漸漸出現(xiàn)了。
漸漸,就是動(dòng)畫的意思。
這樣我們就分解出了3個(gè)簡(jiǎn)單的動(dòng)畫,
為了更直觀,我們給圓、細(xì)線、粗線分別染上不同的顏色,請(qǐng)看下圖
思路是不是已經(jīng)出來(lái)了,圓變扁,大家應(yīng)該想到了基本功里的縮放(transform.scale.y),線消失與線出現(xiàn),有的同學(xué)可能想到了本系列第二篇提到的stroke方案(CAShapeLayer的strokeStart和strokeEnd)。
至此,我們已經(jīng)通過(guò)描述問(wèn)題,把動(dòng)畫分解了,并且發(fā)現(xiàn)可以利用經(jīng)驗(yàn)實(shí)現(xiàn)分解后的動(dòng)畫。
接下來(lái)就是找重要節(jié)點(diǎn)的值了。
我們發(fā)現(xiàn),3者的交界是圓的頂點(diǎn),看來(lái)這是一個(gè)重要的節(jié)點(diǎn)。
在本示例中,我們假設(shè)動(dòng)畫結(jié)束時(shí),圓的縮放系數(shù)scale為0.8,請(qǐng)看下圖
圖中紅點(diǎn)就是圓的頂點(diǎn),由可知,動(dòng)畫開(kāi)始時(shí),頂點(diǎn)y坐標(biāo)就是center.y - r,結(jié)束時(shí)就是在這個(gè)基礎(chǔ)加上縮放的部分2r * (1 - scale),即center + 2r * (1 - scale)處。
再看細(xì)線,細(xì)線的長(zhǎng)度可以從階段3中拿到,動(dòng)畫開(kāi)始時(shí)有長(zhǎng)度(下圖中灰色線),動(dòng)畫結(jié)束時(shí),SS、SE?重合,線就消失了(下圖中藍(lán)色圓的頂點(diǎn)),如圖
再看粗線,?我們假設(shè)結(jié)束時(shí),粗線的底部位于圓未變形時(shí)的圓心處,動(dòng)畫開(kāi)始時(shí)SS、SE重合,線看不到(下圖中灰色圓的頂點(diǎn)),動(dòng)畫結(jié)束時(shí),SS、SE分別到了不同位置,線就出現(xiàn)了(下圖中藍(lán)色線),如圖
?還不是很清楚的同學(xué),可以在紙上畫一畫,畫著畫著就明白了。
至此,關(guān)鍵節(jié)點(diǎn)的值都找到了。
下面我們來(lái)看一個(gè)問(wèn)題。
有的同學(xué)注意到了,這個(gè)圓的縮放和我們?常用的不一樣,?常用的是圓心不變,圓頂點(diǎn)和底點(diǎn)分別向圓心靠攏,形成橢圓的效果。而這個(gè)動(dòng)畫中,是圓底點(diǎn)不動(dòng),頂點(diǎn)和圓心都向底點(diǎn)靠攏。
有經(jīng)驗(yàn)的同學(xué)可能要提到一個(gè)詞了,anchor point ,這個(gè)我先截一張官方文檔Core Animation Programming Guide中的示意圖,圖中用旋轉(zhuǎn)transform示意了anchor point和position。
不嚴(yán)謹(jǐn)?shù)恼f(shuō),transform時(shí),anchor point就是不動(dòng)的那個(gè)點(diǎn),其它點(diǎn)基于anchor point進(jìn)行?計(jì)算(具體大家還是要看一下Core Animation Programming Guide文檔,或它的翻譯版)。
具體到本例中,anchor point應(yīng)該在圓的底點(diǎn),這樣縮放時(shí),就是底點(diǎn)不動(dòng),其他點(diǎn)基于底點(diǎn)計(jì)算了。
理解了anchor point,有的同學(xué)寫出了這一句。
self.arcToCircleLayer.anchorPoint = CGPointMake(0.5, 1);
這一句邏輯是對(duì)的,x等于0.5即anchor point在x軸上位于圓的中心,y?等于1即anchor point在y軸上位于圓的底點(diǎn),和我們前文中的說(shuō)到的要求一致。
但執(zhí)行時(shí),卻會(huì)驚喜的發(fā)現(xiàn),圓的位置發(fā)生了一次突變,如下圖
這是怎么了,想一想,圓的大小與位置??可以認(rèn)為是frame的體現(xiàn),查看一下CALayer的文檔,在frame的Discussion中,有這么一句
the frame rectangle is a computed property that is derived from the values in the bounds, anchorPoint and position properties.
也就是說(shuō)frame是根據(jù)bounds、anchorPoint和position這3個(gè)屬性算出來(lái)的,我們沒(méi)有改變bounds和position,而單單改變了anchorPoint,frame自然也跟著變了。
怎么解決呢,答案依然在frame的Discussion中,如下
When you assign a new value to this property, the layer changes its position and bounds properties to match the rectangle you specified.
文中的this property就是frame,這段文字說(shuō)明,我們給frame指定新值,layer會(huì)自動(dòng)調(diào)整position和bounds。
那就簡(jiǎn)單了,我們?cè)O(shè)置anchor point后,再將圓的frame設(shè)置回之前的值,如下
CGRect frame = self.arcToCircleLayer.frame;
self.arcToCircleLayer.anchorPoint = CGPointMake(0.5, 1);
self.arcToCircleLayer.frame = frame;
篇幅關(guān)系,我就不貼大段代碼了,完整代碼大家可以查看GitHub上OneLoadingAnimation工程的OneLoadingAnimationStep4目錄。
為方便大家查看,我貼了一點(diǎn)代碼,說(shuō)明一下代碼的結(jié)構(gòu),如下
// 第4階段
- (void)doStep4 {
[self doStep4a];
[self doStep4b];
[self doStep4c];
}
// 4階段a:小圓變形
- (void)doStep4a {}
// 4階段b:逐漸消失的豎線
- (void)doStep4b {}
// 4階段c:逐漸出現(xiàn)的豎線
- (void)doStep4c {}
用1、2、3來(lái)表示某階段,用a、b、c來(lái)表示階段內(nèi)某個(gè)動(dòng)畫,這顯然不是良好的命名,但在這個(gè)示例中,這樣也許更清晰。
篇幅所限,我們?cè)谙乱黄性倭暮罄m(xù)的階段,大家有興趣的話,可以分解一下后面的動(dòng)畫,?找一找感覺(jué)。
本文中提到解決問(wèn)題的思路,?相當(dāng)一部分得益于V.Anton Spraul的《像程序員一樣思考》,ISBN號(hào)9787115383396,有興趣的同學(xué)可以看一下,寫的很好。
第三篇到這就告一段落了,非常感謝大家的觀看,我們下一篇再會(huì)。
階段4中我簡(jiǎn)化的部分
大家仔細(xì)查看原動(dòng)效的話,會(huì)發(fā)現(xiàn)圓的變化是不規(guī)則的。
原設(shè)計(jì)更符合直覺(jué),比如大家把球放到地上,拿一個(gè)手指去按它,手指按的部分和球與地面接觸的部分,變形度肯定是不一樣的。
為了實(shí)現(xiàn)簡(jiǎn)單,我將圓的變化處理成了規(guī)則的變化,即從圓漸漸變成了橢圓。
后續(xù)我會(huì)單獨(dú)開(kāi)一篇和大家聊一下圓不規(guī)則變化的實(shí)現(xiàn),敬請(qǐng)期待。
完整代碼
請(qǐng)參考GitHub上OneLoadingAnimation的OneLoadingAnimationStep3和OneLoadingAnimationStep4目錄。
本系列的?傳送門
- 一款Loading動(dòng)畫的實(shí)現(xiàn)思路(?一)
- 一款Loading動(dòng)畫的實(shí)現(xiàn)思路(二)
- 一款Loading動(dòng)畫的實(shí)現(xiàn)思路(三)
- 一款Loading動(dòng)畫的實(shí)現(xiàn)思路(四·完結(jié)篇)
- Loading動(dòng)畫外篇·圓的不規(guī)則變形
鳴謝及推薦
- 原動(dòng)效的設(shè)計(jì)者 moonjoin
- Kitten的A-GUIDE-TO-iOS-ANIMATION
- 喵神發(fā)起的objc中國(guó)的動(dòng)畫部分,都是很優(yōu)秀的譯文,衷心為翻譯的同學(xué)點(diǎn)贊。