Loading動(dòng)畫外篇·圓的不規(guī)則變形

一款Loading動(dòng)畫的實(shí)現(xiàn)思路系列已經(jīng)結(jié)束了膨蛮,非常感謝大家的捧場(chǎng)谦趣。

看過(guò)本系列的同學(xué)可能還記得,我對(duì)原動(dòng)效做了簡(jiǎn)化飞主,
為了讓大家回憶一下狮惜,也讓新來(lái)的同學(xué)有點(diǎn)印象,我先貼一下原動(dòng)畫效果圖:

可以看到碌识,圓被上方的豎線壓扁的時(shí)候碾篡,發(fā)生了不規(guī)則的變形,
具體來(lái)說(shuō)筏餐,圓的頂部比底部變形明顯开泽。

這個(gè)很好理解,我們把球放到地上魁瞪,拿手指去按它穆律,手指按下的地方,肯定要比球和地面接觸的地方變形更明顯导俘。

在Loading系列中我做了簡(jiǎn)化峦耘,圓只是簡(jiǎn)單的變成了橢圓,如下圖:

雖然效果也不錯(cuò)旅薄,但還是有點(diǎn)遺憾贡歧,
所以今天我們一起看一下,圓的不規(guī)則變形的一種實(shí)現(xiàn)方案赋秀,
效果如圖:

好利朵,我們開始吧。

看上去猎莲,這個(gè)動(dòng)畫就是從一個(gè)形狀變成了另一個(gè)形狀绍弟,
熟悉CAShapeLayer的同學(xué),可能想到了它的path屬性著洼,沒(méi)錯(cuò)樟遣,path屬性是支持動(dòng)畫的,
那我們用UIBezierPath分別畫出動(dòng)畫初始身笤、結(jié)束的形狀豹悬,作為path動(dòng)畫的from、to值液荸,應(yīng)該就可以了吧瞻佛。

思路看上去沒(méi)有問(wèn)題,我們來(lái)測(cè)試一下,示意代碼如下:
(p.s. 從本篇開始伤柄,我在文章示例中使用swift代碼绊困,GitHub上會(huì)上傳swift、OC兩個(gè)版本)

@IBAction func startAnimation(sender: AnyObject) {
    // reset
    animationLayer.removeAllAnimations()

    // 初始
    let fromPath = ... // 圓
    // 結(jié)束
    let toPath = ... // 圓變形后的形狀

    // end status
    animationLayer.path = toPath.CGPath

    // animation
    let animation = CABasicAnimation(keyPath: "path")
    animation.duration = 3
    animation.fromValue = fromPath.CGPath
    animation.toValue = toPath.CGPath
    animationLayer.addAnimation(animation, forKey: nil)
}

測(cè)試之前我們最好有個(gè)用例适刀,或者有個(gè)非正式的預(yù)期秤朗。

比如我對(duì)這段代碼的預(yù)期是這樣的:

測(cè)試一下,
結(jié)果是這樣的:

很明顯笔喉,測(cè)試結(jié)果和我們的預(yù)期不一樣;
由此取视,我們得出一個(gè)不嚴(yán)謹(jǐn)?shù)慕Y(jié)論:path動(dòng)畫的效果是不可控的。

也許有的同學(xué)會(huì)說(shuō)常挚,換一種繪制方式贫途,動(dòng)畫效果可能就達(dá)到要求了,
這是可能的待侵,大家可以試一試丢早,
但本篇中,我就不去猜怎么繪制才能達(dá)到要求了秧倾,我嘗試找一個(gè)可控的方案怨酝。

所謂可控,就是動(dòng)畫的每一步那先,形狀的樣子我們都知道农猬。

如果我們能建立起形狀和動(dòng)畫進(jìn)度(用progress代替,取值0.0~1.0)的關(guān)系售淡,那么progress變化時(shí)斤葱,我們重繪形狀,應(yīng)該就可以了揖闸。

思考一下揍堕,形狀和progress建立關(guān)系的難點(diǎn)在哪?

初始我們繪制了一個(gè)圓汤纸,結(jié)束時(shí)我們繪制了一個(gè)不規(guī)則的形狀衩茸,
它們的繪制邏輯是不一樣的,從代碼層面講贮泞,它們各自有一套繪制代碼楞慈。

兩套繪制代碼,聽著不太符合直覺(jué)啃擦。

比較符合直覺(jué)的是囊蓝,我們只有一套繪制代碼,progress是這套代碼的參數(shù)令蛉,progress為0時(shí)聚霜,繪制的是圓,progress為1時(shí),繪制的是不規(guī)則圖形俯萎。

一套代碼可以做到嗎?
可以的运杭,前提是我們要將形狀進(jìn)行分解夫啊,
看上去不一樣的東西,經(jīng)過(guò)分解后辆憔,很可能發(fā)現(xiàn)共同點(diǎn)撇眯。

請(qǐng)看下面的兩張圖:

可以看出,兩圖中的形狀都可以認(rèn)為是由兩條平滑曲線(貝塞爾曲線)構(gòu)成的虱咧。
(本篇不深入貝塞爾曲線熊榛,大家只要知道貝塞爾曲線由起點(diǎn)、終點(diǎn)和N個(gè)控制點(diǎn)決定就好)

假設(shè)藍(lán)線和紅線都以頂部為起點(diǎn)腕巡,以底部為終點(diǎn)玄坦,
動(dòng)畫過(guò)程,其實(shí)就是兩條曲線的起點(diǎn)下移绘沉,終點(diǎn)不動(dòng)煎楣,控制點(diǎn)適當(dāng)變化的過(guò)程。

結(jié)合前面所說(shuō)的车伞,我們可以得到初步的方案:
一套繪制代碼:繪制兩條貝塞爾曲線
動(dòng)畫:貝塞爾曲線的起點(diǎn)择懂、終點(diǎn)、控制點(diǎn)隨progress值變化

大思路有了另玖,
但是貝塞爾曲線的起點(diǎn)困曙、終點(diǎn)、控制點(diǎn)是如何隨progress值變化谦去,才能實(shí)現(xiàn)不規(guī)則變形呢慷丽?

對(duì)我而言,這個(gè)問(wèn)題還是太復(fù)雜了鳄哭,

覺(jué)得復(fù)雜盈魁, 接著分解

規(guī)則的東西實(shí)現(xiàn)起來(lái)窃诉,總會(huì)簡(jiǎn)單一些杨耙,我們先想一想,如何實(shí)現(xiàn)規(guī)則變形飘痛,
打破規(guī)則珊膜,也不難,我們?cè)谝?guī)則變形的基礎(chǔ)上宣脉,破壞一些規(guī)則變形的條件车柠,應(yīng)該就能實(shí)現(xiàn)不規(guī)則變形。

在進(jìn)行下一步的思考之前,我們要先處理一個(gè)問(wèn)題竹祷,
上述思路谈跛,分析是合理的,但存在一個(gè)技術(shù)問(wèn)題:兩條貝塞爾曲線塑陵,是沒(méi)法完美模擬一個(gè)圓的(沒(méi)有深入調(diào)研感憾,有興趣的同學(xué)請(qǐng)搜索“貝塞爾曲線擬合圓”)。

目前的結(jié)論是令花,四條貝塞爾曲線可以比較完美的模擬一個(gè)圓阻桅。

所以我們的方案調(diào)整一下,如下圖:


為了讓大家看的更清晰兼都,我給形狀加上輔助點(diǎn)和輔助線(p.s. 輔助點(diǎn)和輔助線的思路來(lái)自KittenA-GUIDE-TO-iOS-ANIMATION)嫂沉,如下圖:

每條曲線的起點(diǎn)、終點(diǎn)和兩個(gè)控制點(diǎn)扮碧,應(yīng)該比較清晰了趟章。

在處理變形之前,我們先看下慎王,四條貝塞爾曲線怎么模擬出一個(gè)圓尤揣,如圖:


貝塞爾曲線擬合圓

有興趣的同學(xué)可以去找下相關(guān)的數(shù)學(xué)知識(shí),可以搜“貝塞爾曲線擬合圓”柬祠。
此處我們直接引用別人的結(jié)論北戏,如圖所示,第一個(gè)控制點(diǎn)和起點(diǎn)在連線與圓相切方向上漫蛔,距離為半徑r的1/1.8嗜愈,第二個(gè)控制點(diǎn)和終點(diǎn)也是類似的。

代碼中定義的下述常量莽龟,大家就知道是什么意思了:

let controlPointFactor: CGFloat = 1.8

圓模擬出來(lái)了蠕嫁,現(xiàn)在我們來(lái)看一下如何規(guī)則變形,

簡(jiǎn)化一下毯盈,先考慮豎直方向的變形剃毒,我們以圓的底部為原點(diǎn)(0, 0),豎直變形搂赋,可以認(rèn)為是各曲線的起點(diǎn)赘阀、終點(diǎn)和有需要的控制點(diǎn)的y坐標(biāo)均乘于一個(gè)系數(shù),本例中取0.8(豎直方向壓扁)脑奠,那么變形如下圖:

只豎直方向變形

水平方向也類似基公,假設(shè)x方向系數(shù)為1.2(水平方向拉長(zhǎng)),那么變形如下圖:


只水平方向變形

兩者結(jié)合起來(lái)就得到了圓的規(guī)則變形宋欺,如圖(本篇中的規(guī)則變形可以認(rèn)為是對(duì)稱變形轰豆,圓未必變成了數(shù)學(xué)意義上的橢圓):


規(guī)則變化實(shí)現(xiàn)了胰伍,接下來(lái)就該破壞規(guī)則變形的條件了。

大家跑的一樣快酸休,隊(duì)形很整齊骂租,想破壞隊(duì)形,只要讓一個(gè)人跑的比大家快或慢就行了斑司。

我們的動(dòng)效中是頂部變形更明顯渗饮,
所以,我們讓頂點(diǎn)y方向乘的系數(shù)小于0.8就可以了陡厘,也就說(shuō)抽米,頂點(diǎn)相對(duì)于其他點(diǎn)特占,y值變化的幅度更大糙置,比0.8時(shí)的位置更接近原點(diǎn)(底點(diǎn)),如圖:


至此是目,我們的效果就實(shí)現(xiàn)了谤饭。

發(fā)散一下,

頂點(diǎn)跑的慢:


左點(diǎn)不向左跑懊纳,反而向右跑:


不多舉例了揉抵,大家可以看到,這種方案還是比較靈活的嗤疯。

復(fù)雜的形狀可以由更多的貝塞爾曲線組成冤今,只要我們找到貝塞爾曲線的起點(diǎn)、終點(diǎn)茂缚、控制點(diǎn)和progress的關(guān)系戏罢,就可以實(shí)現(xiàn)復(fù)雜可控的形狀動(dòng)畫。

具體代碼實(shí)現(xiàn)脚囊,和本系列主線第一篇是類似的龟糕,采用的重繪方案,示意代碼如下:

// 創(chuàng)建CALayer子類
class CircleIrregularTransformLayer: CALayer

// progress變化時(shí)悔耘,告知layer重繪自己
override static func needsDisplayForKey(key: String) -> Bool {
    switch key {
    case "progress":
        return true
    default:
        break
    }

    return super.needsDisplayForKey(key)
}

// 繪制代碼
override func drawInContext(ctx: CGContext) {
    let path = UIBezierPath()

    // 以底點(diǎn)為原點(diǎn)
    let bottom = ...
    // 控制點(diǎn)偏移距離
    let controlOffsetDistance = radius / 1.8

    // 各點(diǎn)變化系數(shù)
    let xFactor = ... // 根據(jù)progress計(jì)算
    let yFactor = ... // 根據(jù)progress計(jì)算
    // 頂點(diǎn)特殊的變化系數(shù)(破壞規(guī)則變形)
    let topYFactor = ... // 根據(jù)progress計(jì)算

    // 右上弧
    path.addCurveToPoint(dest0, controlPoint1: control0A, controlPoint2: control0B)

    // 左上弧
    path.addCurveToPoint(dest1, controlPoint1: control1A, controlPoint2: control1B)

    // 左下弧
    path.addCurveToPoint(dest2, controlPoint1: control2A, controlPoint2: control2B)

    // 右下弧
    path.addCurveToPoint(dest3, controlPoint1: control3A, controlPoint2: control3B)

    CGContextAddPath(ctx, path.CGPath)

    CGContextSetLineWidth(ctx, lineWidth)
    CGContextSetStrokeColorWithColor(ctx, UIColor.blueColor().CGColor)
    CGContextStrokePath(ctx)

    // 輔助點(diǎn)

    // 輔助線
}

大家在看代碼的時(shí)候讲岁,可能感覺(jué)各點(diǎn)的計(jì)算和文中提到的不完全一致,
文中側(cè)重思路衬以,是以底點(diǎn)為坐標(biāo)系原點(diǎn)(0, 0)缓艳、常規(guī)坐標(biāo)系(x軸向右為正方向,y軸向上為正方向)來(lái)描述的看峻,
而代碼中實(shí)現(xiàn)時(shí)郎任,會(huì)使用UIKit的坐標(biāo)系,底點(diǎn)在superView的坐標(biāo)系中也不會(huì)是(0, 0)备籽,
因此舶治,請(qǐng)放心看代碼分井,思路是一樣的,不一樣的只是實(shí)現(xiàn)上的細(xì)節(jié)霉猛。

本篇作為一款Loading動(dòng)畫系列的補(bǔ)充尺锚,到這就這結(jié)束了,非常感謝大家的捧場(chǎng)惜浅!

大家瘫辩,下個(gè)系列見。

完整代碼

請(qǐng)參考GitHub上OneLoadingAnimation工程中Swift坛悉、OC目錄下的CircleIrregularTransform伐厌。

本系列的?傳送門

鳴謝及推薦

相關(guān)鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市轩猩,隨后出現(xiàn)的幾起案子卷扮,更是在濱河造成了極大的恐慌,老刑警劉巖均践,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晤锹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡彤委,警方通過(guò)查閱死者的電腦和手機(jī)鞭铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)焦影,“玉大人车遂,你說(shuō)我怎么就攤上這事⊥蛋欤” “怎么了艰额?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)椒涯。 經(jīng)常有香客問(wèn)我柄沮,道長(zhǎng),這世上最難降的妖魔是什么废岂? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任祖搓,我火速辦了婚禮,結(jié)果婚禮上湖苞,老公的妹妹穿的比我還像新娘拯欧。我一直安慰自己,他們只是感情好财骨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布镐作。 她就那樣靜靜地躺著藏姐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪该贾。 梳的紋絲不亂的頭發(fā)上羔杨,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音杨蛋,去河邊找鬼兜材。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逞力,可吹牛的內(nèi)容都是我干的曙寡。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼寇荧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼举庶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起砚亭,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灯变,失蹤者是張志新(化名)和其女友劉穎殴玛,沒(méi)想到半個(gè)月后捅膘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滚粟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年寻仗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凡壤。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡署尤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亚侠,到底是詐尸還是另有隱情曹体,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布硝烂,位于F島的核電站箕别,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滞谢。R本人自食惡果不足惜串稀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狮杨。 院中可真熱鬧母截,春花似錦、人聲如沸橄教。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至华烟,卻和暖如春陷遮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垦江。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工帽馋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人比吭。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓绽族,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親衩藤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吧慢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,846評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件赏表、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評(píng)論 4 62
  • (一) 我是一條活在海里的魚 存在的意義是在海里遨游 他說(shuō)检诗,你好自由 你擁有遼闊無(wú)邊的海洋 他說(shuō),你好幸福 生來(lái)瓢剿,...
    梓楊說(shuō)閱讀 282評(píng)論 0 0
  • 我大概是去年十一之后入手的kindle逢慌,希望自己督促自己利用碎片時(shí)間多看書,剛才突然想起算算入手也有半年了间狂,這半年...
    適說(shuō)心語(yǔ)閱讀 343評(píng)論 0 0