iOS動(dòng)畫(huà):通過(guò)例子解構(gòu)動(dòng)畫(huà)實(shí)現(xiàn)

動(dòng)畫(huà)的必要性

動(dòng)畫(huà)的好處我認(rèn)為有兩點(diǎn):1使得app變得更加活潑 2優(yōu)秀的動(dòng)畫(huà)不僅僅是好看,還能提醒用戶(hù):比如一個(gè)有側(cè)邊欄的頁(yè)面,剛進(jìn)入的時(shí)候視圖從左邊彈出。這就可以告訴用戶(hù)我在左邊有個(gè)隱藏的菜單欄,右滑就能看到了。
但物極必反,在我的視角看來(lái)像lottie 的開(kāi)源方airbnb就有點(diǎn)過(guò)猶不及了。過(guò)多的動(dòng)畫(huà),不僅消耗了性能,尤其是大型app。而且奇怪的交互會(huì)導(dǎo)致用戶(hù)的認(rèn)知混亂。

項(xiàng)目中用到的動(dòng)畫(huà)的實(shí)現(xiàn)無(wú)非幾個(gè) 1原生代碼實(shí)現(xiàn) 2lottie 3幀動(dòng)畫(huà) 4 paintcode,這四個(gè)可以實(shí)現(xiàn)所有的動(dòng)畫(huà),我也簡(jiǎn)單地用項(xiàng)目中的4個(gè)動(dòng)畫(huà)來(lái)介紹如何解構(gòu)和實(shí)現(xiàn)。

解構(gòu)動(dòng)畫(huà)1

第一個(gè)例子之前已經(jīng)寫(xiě)了博客
純?cè)a實(shí)現(xiàn),主要用到了mask、貝塞爾曲線(xiàn)。

解構(gòu)動(dòng)畫(huà)2

小球掉落.gif

看完動(dòng)畫(huà)后就可以分析,這里可以分成5個(gè)部分。
1小球掉落 2旋轉(zhuǎn)和展開(kāi) 3上移 4上移之后的label跑馬燈效果 5和下面的tabview聯(lián)動(dòng)
旋轉(zhuǎn)和展開(kāi)這里的實(shí)現(xiàn)和例子1差不多。23也沒(méi)有什么好說(shuō),主要講一下1和4,5。
因?yàn)橹霸诳?code>UIDynamic的庫(kù),所以一看到小球掉落的時(shí)候我腦子里的反應(yīng)就是它。小球的掉落可以仿照真實(shí)事件的掉落,中間的加2個(gè)透明的碰撞物體使得小球偏移方向。

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

UIDynamic很有趣的事情是當(dāng)一個(gè)需要有重力效果的物體,只要讓這個(gè)物體遵從協(xié)議就可以了。這樣的設(shè)計(jì)感覺(jué)就像是我定好一個(gè)協(xié)議,就創(chuàng)造了一個(gè)世界,想要走到我的世界里來(lái)的物體,遵從即可。非常精妙。

當(dāng)然也可以使用CAKeyframeAnimation實(shí)現(xiàn),可能是個(gè)更加精確的辦法,CAKeyframeAnimation繪制多條拋物線(xiàn)路徑也是一個(gè)不錯(cuò)的辦法,使得路徑變得更加可控。

第4點(diǎn)跑馬燈的實(shí)現(xiàn),一個(gè)label移動(dòng)結(jié)束之后,將兩個(gè)label的指針更換一下,這樣就可以實(shí)現(xiàn)左邊的label的指針一直叫l(wèi)eftlabel。

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

第5點(diǎn)和下面的tabview聯(lián)動(dòng)需要提一下的是,這里用了約束實(shí)現(xiàn)動(dòng)畫(huà),以前我不太清楚,約束實(shí)現(xiàn)動(dòng)畫(huà)的好處在哪里,“聯(lián)動(dòng)”。

解構(gòu)動(dòng)畫(huà)3

路徑動(dòng)畫(huà)和轉(zhuǎn)場(chǎng).gif

也是先解構(gòu):1指紋的路徑繪制 2點(diǎn)擊下部view轉(zhuǎn)場(chǎng)到另一個(gè)vc
先看一下第一點(diǎn)的代碼實(shí)現(xiàn)

        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;

是不是瘋了。其實(shí)像上面的代碼其實(shí)不是自己算出來(lái)的,這種復(fù)雜的路徑的實(shí)現(xiàn),可以使用PaintCode來(lái)自動(dòng)生成路徑。使用方法如下
PaintCode使用方法

然后像view變成vc的轉(zhuǎn)場(chǎng),其實(shí)也是present一個(gè)新的vc,數(shù)據(jù)源是一樣的,可以自定義轉(zhuǎn)場(chǎng)的交互。如下

- (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;
}

解構(gòu)動(dòng)畫(huà)4

重寫(xiě)layout 3d.gif

首先頭上的banner實(shí)現(xiàn)是重寫(xiě)collectionview的layout,這里的分析是偏移到一定位置不再偏移,再到相同位置的反面再慢慢回到平的狀態(tài)。
這里拿出來(lái)講主要是想說(shuō)layout的重寫(xiě)真的可以玩出花。代碼如下

  //將小于0的距離轉(zhuǎn)換
            if (distance < 0) {
                distance = distance + cellDistance;
                if (distance >= (cellDistance - offsetExtremum)) {
                    distance =  distance - cellDistance + offsetExtremum;
                    distance = offsetExtremum - distance;
                }
            }
                //開(kāi)始偏移
            if (distance>0 && distance < offsetExtremum) {
                yOffset = distance/_itemSize.width;
                //>34偏移量保持不變
            }else if (distance >= offsetExtremum && distance< cellDistance - offsetExtremum){
                yOffset = offsetExtremum/_itemSize.width;
                //復(fù)原
            }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下沉?,F(xiàn)在也經(jīng)常能看到這種設(shè)計(jì),包括蝦米的首頁(yè)。這里使用的是上部放一個(gè)透明的headview,后來(lái)也加上了有閾值的交互。

尾聲

講的比較簡(jiǎn)單,實(shí)際過(guò)程中遇到的坑也有很多。主要想說(shuō)的是遇到一個(gè)動(dòng)畫(huà),如何解構(gòu),寫(xiě)的多了,看到交互就能想到最好的實(shí)現(xiàn)辦法是什么。
推薦動(dòng)畫(huà)的一本書(shū) 剛看完 iOS核心動(dòng)畫(huà)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫(huà)全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,688評(píng)論 6 30
  • 各位絨布峰針的家人們,大家好 經(jīng)過(guò)這一周緊張的特種兵訓(xùn)練,讓我增加了不少的知識(shí),在這之前,我對(duì)微商這一職業(yè)認(rèn)識(shí)膚淺...
    木槿木可閱讀 233評(píng)論 0 0
  • 今天早上在我們的夏季課程學(xué)員微信群里分享了一個(gè)話(huà)題“孩子多好才夠好?” 話(huà)題的開(kāi)始是由我講的關(guān)于女兒考四級(jí)的故事引...
    emmanuelleZHAO閱讀 204評(píng)論 0 1
  • 減肥的一個(gè)重要問(wèn)題,就是能不能吃肉? 答案是:當(dāng)然是要吃!肉是蛋白質(zhì)的重要來(lái)源,因?yàn)闇p肥而全素,導(dǎo)致了營(yíng)養(yǎng)不良,如...
    莠子閱讀 904評(píng)論 0 3

友情鏈接更多精彩內(nèi)容