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核心动画

你可能感兴趣的:(iOS动画:通过例子解构动画实现)