動畫的必要性
動畫的好處我認為有兩點: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核心動畫