Loading動畫外篇·圓的不規則變形

一款Loading動畫的實現思路系列已經結束了,非常感謝大家的捧場。

看過本系列的同學可能還記得,我對原動效做了簡化,
為了讓大家回憶一下,也讓新來的同學有點印象,我先貼一下原動畫效果圖:

可以看到,圓被上方的豎線壓扁的時候,發生了不規則的變形,
具體來說,圓的頂部比底部變形明顯。

這個很好理解,我們把球放到地上,拿手指去按它,手指按下的地方,肯定要比球和地面接觸的地方變形更明顯。

在Loading系列中我做了簡化,圓只是簡單的變成了橢圓,如下圖:

雖然效果也不錯,但還是有點遺憾,
所以今天我們一起看一下,圓的不規則變形的一種實現方案,
效果如圖:

好,我們開始吧。

看上去,這個動畫就是從一個形狀變成了另一個形狀,
熟悉CAShapeLayer的同學,可能想到了它的path屬性,沒錯,path屬性是支持動畫的,
那我們用UIBezierPath分別畫出動畫初始、結束的形狀,作為path動畫的from、to值,應該就可以了吧。

思路看上去沒有問題,我們來測試一下,示意代碼如下:
(p.s. 從本篇開始,我在文章示例中使用swift代碼,GitHub上會上傳swift、OC兩個版本)

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

    // 初始
    let fromPath = ... // 圓
    // 結束
    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)
}

測試之前我們最好有個用例,或者有個非正式的預期

比如我對這段代碼的預期是這樣的:

測試一下,
結果是這樣的:

很明顯,測試結果和我們的預期不一樣;
由此,我們得出一個不嚴謹的結論:path動畫的效果是不可控的。

也許有的同學會說,換一種繪制方式,動畫效果可能就達到要求了,
這是可能的,大家可以試一試,
但本篇中,我就不去猜怎么繪制才能達到要求了,我嘗試找一個可控的方案。

所謂可控,就是動畫的每一步,形狀的樣子我們都知道。

如果我們能建立起形狀和動畫進度(用progress代替,取值0.0~1.0)的關系,那么progress變化時,我們重繪形狀,應該就可以了。

思考一下,形狀和progress建立關系的難點在哪?

初始我們繪制了一個圓,結束時我們繪制了一個不規則的形狀,
它們的繪制邏輯是不一樣的,從代碼層面講,它們各自有一套繪制代碼。

兩套繪制代碼,聽著不太符合直覺。

比較符合直覺的是,我們只有一套繪制代碼,progress是這套代碼的參數,progress為0時,繪制的是圓,progress為1時,繪制的是不規則圖形。

一套代碼可以做到嗎?
可以的,前提是我們要將形狀進行分解
看上去不一樣的東西,經過分解后,很可能發現共同點

請看下面的兩張圖:

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

假設藍線和紅線都以頂部為起點,以底部為終點,
動畫過程,其實就是兩條曲線的起點下移,終點不動,控制點適當變化的過程。

結合前面所說的,我們可以得到初步的方案:
一套繪制代碼:繪制兩條貝塞爾曲線
動畫:貝塞爾曲線的起點、終點、控制點隨progress值變化

大思路有了,
但是貝塞爾曲線的起點、終點、控制點是如何隨progress值變化,才能實現不規則變形呢?

對我而言,這個問題還是太復雜了,

覺得復雜, 接著分解

規則的東西實現起來,總會簡單一些,我們先想一想,如何實現規則變形,
打破規則,也不難,我們在規則變形的基礎上,破壞一些規則變形的條件,應該就能實現不規則變形。

在進行下一步的思考之前,我們要先處理一個問題,
上述思路,分析是合理的,但存在一個技術問題:兩條貝塞爾曲線,是沒法完美模擬一個圓的(沒有深入調研,有興趣的同學請搜索“貝塞爾曲線擬合圓”)。

目前的結論是,四條貝塞爾曲線可以比較完美的模擬一個圓。

所以我們的方案調整一下,如下圖:


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

每條曲線的起點、終點和兩個控制點,應該比較清晰了。

在處理變形之前,我們先看下,四條貝塞爾曲線怎么模擬出一個圓,如圖:


貝塞爾曲線擬合圓

有興趣的同學可以去找下相關的數學知識,可以搜“貝塞爾曲線擬合圓”。
此處我們直接引用別人的結論,如圖所示,第一個控制點和起點在連線與圓相切方向上,距離為半徑r的1/1.8,第二個控制點和終點也是類似的。

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

let controlPointFactor: CGFloat = 1.8

圓模擬出來了,現在我們來看一下如何規則變形,

簡化一下,先考慮豎直方向的變形,我們以圓的底部為原點(0, 0),豎直變形,可以認為是各曲線的起點、終點和有需要的控制點的y坐標均乘于一個系數,本例中取0.8(豎直方向壓扁),那么變形如下圖:

只豎直方向變形

水平方向也類似,假設x方向系數為1.2(水平方向拉長),那么變形如下圖:


只水平方向變形

兩者結合起來就得到了圓的規則變形,如圖(本篇中的規則變形可以認為是對稱變形,圓未必變成了數學意義上的橢圓):


規則變化實現了,接下來就該破壞規則變形的條件了。

大家跑的一樣快,隊形很整齊,想破壞隊形,只要讓一個人跑的比大家快或慢就行了。

我們的動效中是頂部變形更明顯,
所以,我們讓頂點y方向乘的系數小于0.8就可以了,也就說,頂點相對于其他點,y值變化的幅度更大,比0.8時的位置更接近原點(底點),如圖:


至此,我們的效果就實現了。

發散一下,

頂點跑的慢:


左點不向左跑,反而向右跑:


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

復雜的形狀可以由更多的貝塞爾曲線組成,只要我們找到貝塞爾曲線的起點、終點、控制點和progress的關系,就可以實現復雜可控的形狀動畫。

具體代碼實現,和本系列主線第一篇是類似的,采用的重繪方案,示意代碼如下:

// 創建CALayer子類
class CircleIrregularTransformLayer: CALayer

// progress變化時,告知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()

    // 以底點為原點
    let bottom = ...
    // 控制點偏移距離
    let controlOffsetDistance = radius / 1.8

    // 各點變化系數
    let xFactor = ... // 根據progress計算
    let yFactor = ... // 根據progress計算
    // 頂點特殊的變化系數(破壞規則變形)
    let topYFactor = ... // 根據progress計算

    // 右上弧
    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)

    // 輔助點

    // 輔助線
}

大家在看代碼的時候,可能感覺各點的計算和文中提到的不完全一致,
文中側重思路,是以底點為坐標系原點(0, 0)、常規坐標系(x軸向右為正方向,y軸向上為正方向)來描述的,
而代碼中實現時,會使用UIKit的坐標系,底點在superView的坐標系中也不會是(0, 0),
因此,請放心看代碼,思路是一樣的,不一樣的只是實現上的細節。

本篇作為一款Loading動畫系列的補充,到這就這結束了,非常感謝大家的捧場!

大家,下個系列見。

完整代碼

請參考GitHub上OneLoadingAnimation工程中Swift、OC目錄下的CircleIrregularTransform。

本系列的?傳送門

鳴謝及推薦

相關鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,722評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,170評論 4 61
  • (一) 我是一條活在海里的魚 存在的意義是在海里遨游 他說,你好自由 你擁有遼闊無邊的海洋 他說,你好幸福 生來,...
    梓楊說閱讀 296評論 0 0
  • 我大概是去年十一之后入手的kindle,希望自己督促自己利用碎片時間多看書,剛才突然想起算算入手也有半年了,這半年...
    適說心語閱讀 348評論 0 0