iOS動畫:通過例子解構動畫實現

動畫的必要性

動畫的好處我認為有兩點:1使得app變得更加活潑 2優秀的動畫不僅僅是好看,還能提醒用戶:比如一個有側邊欄的頁面,剛進入的時候視圖從左邊彈出。這就可以告訴用戶我在左邊有個隱藏的菜單欄,右滑就能看到了。
但物極必反,在我的視角看來像lottie 的開源方airbnb就有點過猶不及了。過多的動畫,不僅消耗了性能,尤其是大型app。而且奇怪的交互會導致用戶的認知混亂。

項目中用到的動畫的實現無非幾個 1原生代碼實現 2lottie 3幀動畫 4 paintcode,這四個可以實現所有的動畫,我也簡單地用項目中的4個動畫來介紹如何解構和實現。

解構動畫1

第一個例子之前已經寫了博客
純原生代碼實現,主要用到了mask、貝塞爾曲線。

解構動畫2

小球掉落.gif

看完動畫后就可以分析,這里可以分成5個部分。
1小球掉落 2旋轉和展開 3上移 4上移之后的label跑馬燈效果 5和下面的tabview聯動
旋轉和展開這里的實現和例子1差不多。23也沒有什么好說,主要講一下1和4,5。
因為之前在看UIDynamic的庫,所以一看到小球掉落的時候我腦子里的反應就是它。小球的掉落可以仿照真實事件的掉落,中間的加2個透明的碰撞物體使得小球偏移方向。

- (void)addBehavior:(UIDynamicBehavior *)behavior;//添加指定的行為動畫
- (void)removeBehavior:(UIDynamicBehavior *)behavior;//移除指定的行為動畫
- (void)removeAllBehaviors;//移除所有的行為

UIDynamic很有趣的事情是當一個需要有重力效果的物體,只要讓這個物體遵從協議就可以了。這樣的設計感覺就像是我定好一個協議,就創造了一個世界,想要走到我的世界里來的物體,遵從即可。非常精妙。

當然也可以使用CAKeyframeAnimation實現,可能是個更加精確的辦法,CAKeyframeAnimation繪制多條拋物線路徑也是一個不錯的辦法,使得路徑變得更加可控。

第4點跑馬燈的實現,一個label移動結束之后,將兩個label的指針更換一下,這樣就可以實現左邊的label的指針一直叫leftlabel。

        //交換指針
        UILabel * tempLbl = self.lblTextHornRight;
        self.lblTextHornRight = self.lblTextHornLeft;
        self.lblTextHornLeft = tempLbl;
        
        //將左邊的移到右邊的末尾
        CGRect rect = self.lblTextHornLeft.frame;
       

第5點和下面的tabview聯動需要提一下的是,這里用了約束實現動畫,以前我不太清楚,約束實現動畫的好處在哪里,“聯動”。

解構動畫3

路徑動畫和轉場.gif

也是先解構:1指紋的路徑繪制 2點擊下部view轉場到另一個vc
先看一下第一點的代碼實現

        UIBezierPath *leftPath = [UIBezierPath bezierPath];
        [leftPath moveToPoint: CGPointMake(82.27, 80.3)];
        [leftPath addCurveToPoint: CGPointMake(82.27, 86.42) controlPoint1: CGPointMake(82.27, 81.67) controlPoint2: CGPointMake(82.29, 83.88)];
        [leftPath addCurveToPoint: CGPointMake(82.2, 92.07) controlPoint1: CGPointMake(82.25, 88.2) controlPoint2: CGPointMake(82.18, 90.14)];
        [leftPath addCurveToPoint: CGPointMake(82.8, 100.56) controlPoint1: CGPointMake(82.24, 95.12) controlPoint2: CGPointMake(82.25, 98.18)];
        [leftPath addCurveToPoint: CGPointMake(83.18, 101.95) controlPoint1: CGPointMake(82.91, 101.03) controlPoint2: CGPointMake(83.05, 101.49)];
        [leftPath addCurveToPoint: CGPointMake(83.46, 102.78) controlPoint1: CGPointMake(83.26, 102.22) controlPoint2: CGPointMake(83.37, 102.51)];
        [leftPath addCurveToPoint: CGPointMake(83.99, 104) controlPoint1: CGPointMake(83.63, 103.26) controlPoint2: CGPointMake(83.81, 103.59)];
        [leftPath addCurveToPoint: CGPointMake(84.46, 104.83) controlPoint1: CGPointMake(84.11, 104.28) controlPoint2: CGPointMake(84.33, 104.63)];
        [leftPath addCurveToPoint: CGPointMake(91.59, 110.93) controlPoint1: CGPointMake(85.98, 107.21) controlPoint2: CGPointMake(88.07, 109.59)];
        [leftPath addCurveToPoint: CGPointMake(93.8, 111.57) controlPoint1: CGPointMake(92.12, 111.13) controlPoint2: CGPointMake(92.87, 111.39)];
        [leftPath addCurveToPoint: CGPointMake(96.22, 111.89) controlPoint1: CGPointMake(94.27, 111.67) controlPoint2: CGPointMake(95.07, 111.77)];
        [leftPath addCurveToPoint: CGPointMake(99.6, 111.72) controlPoint1: CGPointMake(97.73, 111.92) controlPoint2: CGPointMake(98.86, 111.86)];
        [leftPath addCurveToPoint: CGPointMake(103.87, 110.39) controlPoint1: CGPointMake(101.18, 111.42) controlPoint2: CGPointMake(102.98, 110.83)];
        [leftPath addCurveToPoint: CGPointMake(107.63, 107.67) controlPoint1: CGPointMake(104.5, 110.07) controlPoint2: CGPointMake(106.18, 109.09)];
        [leftPath addCurveToPoint: CGPointMake(109.5, 105.22) controlPoint1: CGPointMake(108.36, 106.96) controlPoint2: CGPointMake(108.95, 106.05)];
        [leftPath addCurveToPoint: CGPointMake(111.06, 101.95) controlPoint1: CGPointMake(110, 104.48) controlPoint2: CGPointMake(110.6, 103.44)];
        [leftPath addCurveToPoint: CGPointMake(111.78, 98.76) controlPoint1: CGPointMake(111.25, 101.35) controlPoint2: CGPointMake(111.49, 100.29)];
        [leftPath addCurveToPoint: CGPointMake(111.98, 89.23) controlPoint1: CGPointMake(111.91, 94.53) controlPoint2: CGPointMake(111.98, 91.36)];
        [leftPath addCurveToPoint: CGPointMake(111.98, 81.15) controlPoint1: CGPointMake(111.98, 87.4) controlPoint2: CGPointMake(112.08, 84.71)];
        [leftPath addCurveToPoint: CGPointMake(111.86, 79.25) controlPoint1: CGPointMake(111.97, 80.77) controlPoint2: CGPointMake(111.93, 80.14)];
        [leftPath addCurveToPoint: CGPointMake(111.16, 74.49) controlPoint1: CGPointMake(111.59, 76.99) controlPoint2: CGPointMake(111.36, 75.4)];
        [leftPath addCurveToPoint: CGPointMake(109.92, 70.36) controlPoint1: CGPointMake(110.79, 72.76) controlPoint2: CGPointMake(110.29, 71.34)];
        [leftPath addCurveToPoint: CGPointMake(107.55, 65.54) controlPoint1: CGPointMake(109.47, 69.17) controlPoint2: CGPointMake(108.7, 67.49)];
        [leftPath addCurveToPoint: CGPointMake(105.55, 62.69) controlPoint1: CGPointMake(107.19, 64.94) controlPoint2: CGPointMake(106.52, 63.98)];
        [leftPath addCurveToPoint: CGPointMake(103.48, 60.3) controlPoint1: CGPointMake(104.6, 61.54) controlPoint2: CGPointMake(103.91, 60.75)];
        [leftPath addCurveToPoint: CGPointMake(100.66, 57.76) controlPoint1: CGPointMake(102.33, 59.1) controlPoint2: CGPointMake(101.34, 58.29)];
        [leftPath addCurveToPoint: CGPointMake(95.15, 54.37) controlPoint1: CGPointMake(98.64, 56.15) controlPoint2: CGPointMake(96.61, 55.07)];
        [leftPath addCurveToPoint: CGPointMake(87.8, 52.05) controlPoint1: CGPointMake(93.41, 53.53) controlPoint2: CGPointMake(90.94, 52.59)];
        [leftPath addCurveToPoint: CGPointMake(84.57, 51.57) controlPoint1: CGPointMake(87.19, 51.94) controlPoint2: CGPointMake(85.64, 51.65)];
        [leftPath addCurveToPoint: CGPointMake(82.62, 51.5) controlPoint1: CGPointMake(84.07, 51.53) controlPoint2: CGPointMake(83.42, 51.51)];
        [leftPath addCurveToPoint: CGPointMake(80.2, 51.57) controlPoint1: CGPointMake(81.53, 51.5) controlPoint2: CGPointMake(80.72, 51.53)];
        [leftPath addCurveToPoint: CGPointMake(77.62, 51.88) controlPoint1: CGPointMake(79.26, 51.65) controlPoint2: CGPointMake(78.4, 51.76)];
        [leftPath addCurveToPoint: CGPointMake(74.3, 52.58) controlPoint1: CGPointMake(75.7, 52.15) controlPoint2: CGPointMake(75.27, 52.33)];
        [leftPath addCurveToPoint: CGPointMake(70.58, 53.9) controlPoint1: CGPointMake(73.21, 52.86) controlPoint2: CGPointMake(71.96, 53.27)];
        [leftPath addCurveToPoint: CGPointMake(68.5, 54.93) controlPoint1: CGPointMake(70.11, 54.12) controlPoint2: CGPointMake(69.36, 54.45)];
        [leftPath addCurveToPoint: CGPointMake(67.2, 55.68) controlPoint1: CGPointMake(68.23, 55.07) controlPoint2: CGPointMake(67.8, 55.33)];
        leftPath.miterLimit = 4;
        leftPath.usesEvenOddFillRule = YES;

是不是瘋了。其實像上面的代碼其實不是自己算出來的,這種復雜的路徑的實現,可以使用PaintCode來自動生成路徑。使用方法如下
PaintCode使用方法

然后像view變成vc的轉場,其實也是present一個新的vc,數據源是一樣的,可以自定義轉場的交互。如下

- (void)onImageViewTap:(UITapGestureRecognizer *)tap {
    self.selectedView = (UIImageView *)tap.view;
    
    PictureBroswerViewController *vc = [[PictureBroswerViewController alloc] init];
    vc.image = [self.selectedView image];
    vc.transitioningDelegate = self;
    [self presentViewController:vc animated:YES completion:nil];
}

- (PictureBroswerTransitionAnimator *)generateAnimatorWithPresenting:(BOOL)presenting {
    PictureBroswerTransitionAnimator *animator = [[PictureBroswerTransitionAnimator alloc] init];
    animator.presenting = presenting;
    animator.originFrame = [self.selectedView.superview convertRect:self.selectedView.frame toView:nil];
    return animator;
}

解構動畫4

重寫layout 3d.gif

首先頭上的banner實現是重寫collectionview的layout,這里的分析是偏移到一定位置不再偏移,再到相同位置的反面再慢慢回到平的狀態。
這里拿出來講主要是想說layout的重寫真的可以玩出花。代碼如下

  //將小于0的距離轉換
            if (distance < 0) {
                distance = distance + cellDistance;
                if (distance >= (cellDistance - offsetExtremum)) {
                    distance =  distance - cellDistance + offsetExtremum;
                    distance = offsetExtremum - distance;
                }
            }
                //開始偏移
            if (distance>0 && distance < offsetExtremum) {
                yOffset = distance/_itemSize.width;
                //>34偏移量保持不變
            }else if (distance >= offsetExtremum && distance< cellDistance - offsetExtremum){
                yOffset = offsetExtremum/_itemSize.width;
                //復原
            }else if (distance >= (cellDistance - offsetExtremum) && distance <= cellDistance){
                distance = cellDistance - distance;
                yOffset = distance/_itemSize.width;
            }
            else{
                yOffset = 0;
            }

    CATransform3D trans = CATransform3DIdentity;
            trans.m34 = -1/100.0;
            trans = CATransform3DRotate(trans, M_PI/9.0*yOffset, 0, 1, 0);
            attr.transform3D =trans;
            attr.transform3D = CATransform3DScale(trans, zoom, zoom,zoom);

其次下面的視圖上移,banner下沉。現在也經常能看到這種設計,包括蝦米的首頁。這里使用的是上部放一個透明的headview,后來也加上了有閾值的交互。

尾聲

講的比較簡單,實際過程中遇到的坑也有很多。主要想說的是遇到一個動畫,如何解構,寫的多了,看到交互就能想到最好的實現辦法是什么。
推薦動畫的一本書 剛看完 iOS核心動畫

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

推薦閱讀更多精彩內容