动画的必要性
动画的好处我认为有两点:1使得app变得更加活泼 2优秀的动画不仅仅是好看,还能提醒用户:比如一个有侧边栏的页面,刚进入的时候视图从左边弹出。这就可以告诉用户我在左边有个隐藏的菜单栏,右滑就能看到了。
但物极必反,在我的视角看来像lottie 的开源方airbnb就有点过犹不及了。过多的动画,不仅消耗了性能,尤其是大型app。而且奇怪的交互会导致用户的认知混乱。
项目中用到的动画的实现无非几个 1原生代码实现 2lottie 3帧动画 4 paintcode,这四个可以实现所有的动画,我也简单地用项目中的4个动画来介绍如何解构和实现。
解构动画1
第一个例子之前已经写了博客
纯原生代码实现,主要用到了mask、贝塞尔曲线。
解构动画2
看完动画后就可以分析,这里可以分成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
也是先解构: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
首先头上的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核心动画