用代碼來實(shí)現(xiàn)你喜歡的動(dòng)畫效果 — 蟲兒跑

作者建議讀者在PC上閱讀,字?jǐn)?shù)太多手機(jī)閱讀起來不是很好。

文檔更新說明

  • 2016-03-25 完成全文
  • 2016-03-24 完成4.3
  • 2016-03-22 完成4.1,4.2
  • 2016-03-21 初稿

前言

接觸iOS應(yīng)用開發(fā)不久,對于很多我暫時(shí)不清楚怎么實(shí)現(xiàn)的功能效果都挺感興趣的.iOS開發(fā)中,動(dòng)畫特效是必不可少的一部分,最近學(xué)習(xí)了一下動(dòng)畫,順便花了點(diǎn)時(shí)間實(shí)現(xiàn)了一個(gè)蟲子嚅動(dòng)的動(dòng)畫效果并寫成一個(gè)指示器(HUD).本文將講述怎么學(xué)習(xí)動(dòng)畫設(shè)計(jì),最終的效果就是要做到看上一個(gè)喜歡的動(dòng)畫,然后能用代碼在iOS中實(shí)現(xiàn)他,最后能夠在實(shí)際項(xiàng)目使用.

看上的動(dòng)畫

這是一篇和動(dòng)畫有關(guān)的文章,那我就在文章開頭放出這次想要實(shí)現(xiàn)的動(dòng)畫吧,大家也好思考一下具體怎么去實(shí)現(xiàn).下面是我在豆瓣網(wǎng)上看到的一張動(dòng)畫圖,覺得挺有意思的,于是我打算用程序?qū)崿F(xiàn)它.我給它取了一個(gè)名字,蟲兒跑,一只勤奮的蟲子.


a-diligent-worm.gif

思考

了解iOS中實(shí)現(xiàn)動(dòng)畫的方法

iOS中的動(dòng)畫主要有View級(jí)別,Layer級(jí)別.View級(jí)別動(dòng)畫可以利用UIView的類中animation開頭的類方法實(shí)現(xiàn),使用簡單方便,但是無法操作更多細(xì)節(jié),它其實(shí)就是Core Animation的封裝.Layer級(jí)別的動(dòng)畫相對于View級(jí)別來說要底層一些,主要是使用Core Animation來實(shí)現(xiàn).相關(guān)CA類如下圖:

  
Core-Animation-Classes.png

CAAnimation:核心動(dòng)畫的基礎(chǔ)類,不能直接使用,負(fù)責(zé)動(dòng)畫運(yùn)行時(shí)間、速度的控制,本身實(shí)現(xiàn)了CAMediaTiming協(xié)議。
CAPropertyAnimation:屬性動(dòng)畫的基類(通過屬性進(jìn)行動(dòng)畫設(shè)置,注意是可動(dòng)畫屬性),不能直接使用。
CAAnimationGroup:動(dòng)畫組,動(dòng)畫組是一種組合模式設(shè)計(jì),可以通過動(dòng)畫組來進(jìn)行所有動(dòng)畫行為的統(tǒng)一控制,組中所有動(dòng)畫效果可以并發(fā)執(zhí)行。
CATransition:轉(zhuǎn)場動(dòng)畫,主要通過濾鏡進(jìn)行動(dòng)畫效果設(shè)置。
CABasicAnimation:基礎(chǔ)動(dòng)畫,通過屬性修改進(jìn)行動(dòng)畫參數(shù)控制,只有初始狀態(tài)和結(jié)束狀態(tài)。
CAKeyframeAnimation:關(guān)鍵幀動(dòng)畫,同樣是通過屬性進(jìn)行動(dòng)畫參數(shù)控制,但是同基礎(chǔ)動(dòng)畫不同的是它可以有多個(gè)狀態(tài)控制。

根據(jù)實(shí)際場景,本文主要是在CALayer的子類CAShapeLayer(看名字很容易看出這是專門用作圖形處理的子類)中利用CABasicAnimation, CAAnimationGroup來實(shí)現(xiàn)動(dòng)畫效果.更多iOS動(dòng)畫知識(shí),可以參考文后的閱讀推薦
  CABasicAnimation類有幾個(gè)屬性要特別留意,分別是fromValue和toValue,這兩個(gè)基本就是CABasicAnimation的核心了,其余大部分是繼承子父類的.這里要重點(diǎn)指出的是創(chuàng)建CABasicAnimation對象時(shí)候要指出keyPath,用來指明動(dòng)畫是平移縮放旋轉(zhuǎn)還是繪制等.其中keyPath可以設(shè)置為strokeStart, strokeEnd,而CAShapeLayer也有兩個(gè)屬性分別是strokeStart, strokeEnd.網(wǎng)上很多文章介紹這些屬性的作用了.我這里就不重復(fù)描述了,感覺這種東西光自己描述真的是很難說清楚,我發(fā)現(xiàn)有一個(gè)特別便捷直觀的方法,那就是你在代碼中自己測試觀察.所以我特別寫了兩段測試的代碼.大家也可以在我的GitHub上找到可以直接運(yùn)行的代碼.

CAShapeLayer *firstWormShapeLayer = [[CAShapeLayer alloc] init];
    firstWormShapeLayer.path = [self testPath];
    firstWormShapeLayer.lineWidth = 2;
    firstWormShapeLayer.lineCap = kCALineCapRound;
    firstWormShapeLayer.strokeColor = [UIColor redColor].CGColor;
    firstWormShapeLayer.fillColor = [UIColor clearColor].CGColor;
    firstWormShapeLayer.actions = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNull null],@"strokeStart",[NSNull null],@"strokeEnd", nil];

//self 是一個(gè)UIView對象
//@property (nonatomic,strong) CAShapeLayer *firstWormShapeLayer;
[self.layer addSublayer:firstWormShapeLayer];
self.firstWormShapeLayer = firstWormShapeLayer;
[self testAnimation];
 
 #pragma mark - 測試用
 // 生成一條用于制作動(dòng)畫的路徑
-(CGPathRef)testPath{
    //畫一個(gè)半圓
    UIBezierPath* wormPath = UIBezierPath.bezierPath;
    [wormPath moveToPoint: CGPointMake(5,20)];
    [wormPath addLineToPoint:CGPointMake(35, 20)];
    CGPathRef path = wormPath.CGPath;
    return path;
}

// 開始動(dòng)畫效果
-(void)testAnimation{
    CABasicAnimation *strokeStartAm = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    //時(shí)間函數(shù),用于描述動(dòng)畫的速度和時(shí)間的關(guān)系,可以先忽略
    strokeStartAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    strokeStartAm.toValue = [NSNumber numberWithFloat:0.2];
    strokeStartAm.fromValue = [NSNumber numberWithFloat:1];
    //一次動(dòng)畫的執(zhí)行時(shí)間
    strokeStartAm.duration = 2;
    //動(dòng)畫重復(fù)次數(shù)
    strokeStartAm.repeatCount = 100;
    
    CABasicAnimation *strokeEndAm = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm.toValue = [NSNumber numberWithFloat:1];
    strokeEndAm.fromValue = [NSNumber numberWithFloat:0];
    strokeEndAm.duration = 2;
    strokeEndAm.repeatCount = 100;
    
    [self.firstWormShapeLayer addAnimation:strokeEndAm forKey:nil];
}

哪些工具可以利用?

要實(shí)現(xiàn)一個(gè)動(dòng)畫,最重要的就是確定動(dòng)畫的速度與時(shí)間的關(guān)系,確定動(dòng)畫的基本元素.比如動(dòng)畫的基本形狀,字形,路徑等.這里我找到了幾種相當(dāng)不錯(cuò)的工具.

  1. CAMediaTimingFunction playground
    可視化觀察timingFunction的曲線
  2. Sketch
    這是一款Mac上的矢量圖繪制工具,可以到處SVG格式的文件
  3. PaintCode
    用于將SVG文件轉(zhuǎn)換成OC的路徑代碼(提醒一下,PaintCode這個(gè)軟件制作出來的路徑繪制順序可能不是你想要的)
      本文將要實(shí)現(xiàn)的動(dòng)畫效果看上去像是一個(gè)半圓,所以應(yīng)該是用不上路徑代碼生成工具.

iOS動(dòng)畫的幾個(gè)重要概念

  1. 關(guān)鍵幀:一個(gè)動(dòng)畫可能是單一的變化,等比例放大縮小,也可能是由好幾個(gè)變化組成,比如先放大,再縮小,再平移.在iOS動(dòng)畫設(shè)計(jì)中,系統(tǒng)提供一些設(shè)置關(guān)鍵幀方法,你只要定義好關(guān)鍵幀和關(guān)鍵幀幀之間的時(shí)間分配,系統(tǒng)就會(huì)根據(jù)關(guān)鍵幀的區(qū)別自動(dòng)補(bǔ)全過度幀,達(dá)到一個(gè)動(dòng)畫流暢的效果.
  2. 層:4.1小節(jié)中提到,在CALayer上才能實(shí)現(xiàn)更多動(dòng)畫的細(xì)節(jié).View的層概念類似PhotoShop中的圖層,一個(gè)圖像的最終效果可以通過多個(gè)層重疊實(shí)現(xiàn)的,同樣的一個(gè)View在屏幕上顯示的效果除了View上面的內(nèi)容之外,還有一個(gè)很大的因素就是這個(gè)View的層里內(nèi)容.
  3. 時(shí)間函數(shù):動(dòng)畫總會(huì)在設(shè)定的時(shí)間長度內(nèi)執(zhí)行完,在這段時(shí)間內(nèi),動(dòng)畫的變化有多大,變化的速度有多快,這主要取決于時(shí)間函數(shù).如果時(shí)間函數(shù)是線性函數(shù),那表示動(dòng)畫的變化速度在這一段時(shí)間內(nèi)是均勻變化的.如果是曲線的,那又是另一種效果,下面給出系統(tǒng)提供的5種時(shí)間函數(shù)的函數(shù)圖像,方便大家理解.


    kCAMediaTimingFunctionLinear.png

    線型函數(shù)

kCAMediaTimingFunctionEaseIn.png

緩入

kCAMediaTimingFunctionEaseOut.png

緩出

kCAMediaTimingFunctionEaseInEaseOut.png

慢入慢出

kCAMediaTimingFunctionDefault.png

系統(tǒng)默認(rèn)的時(shí)間函數(shù)

系統(tǒng)還提供了一個(gè)設(shè)置時(shí)間函數(shù)的方法[CAMediaTimingFunction functionWithControlPoints::::],具體用法參考CAMediaTimingFunction playground

  1. fillMode:填充模式是CAMediaTiming協(xié)議里的屬性,所有的Core Animation動(dòng)畫類都有,它非常重要又不好直接通過文檔理解,所以有必要解釋一下.
    kCAFillModeRemoved:默認(rèn)是這個(gè)屬性,動(dòng)畫完成后就從layer中移除了.你看到的效果就是動(dòng)畫結(jié)束后,你在層中可以看到內(nèi)容變成了你繪制的完整圖形,這經(jīng)常發(fā)生在你有多個(gè)動(dòng)畫對象要同時(shí)執(zhí)行動(dòng)畫,但是這些對象的時(shí)間長度不同,造成一些對象執(zhí)行完了就顯示完整圖形了.
    kCAFillModeForwards:當(dāng)動(dòng)畫結(jié)束后,保留動(dòng)畫結(jié)束時(shí)的模樣.
    kCAFillModeBackwards:動(dòng)畫在開始前就顯示出動(dòng)畫要開始時(shí)的模樣.比如你動(dòng)畫延遲2秒執(zhí)行,那么動(dòng)畫在沒開始執(zhí)行這段時(shí)間的模樣就是動(dòng)畫開始執(zhí)行時(shí)的樣子.
    kCAFillModeBoth:同時(shí)具備kCAFillModeForwards和kCAFillModeBackwards的效果
  2. 動(dòng)畫組: iOS動(dòng)畫中,可以使用CAAnimationGroup來實(shí)現(xiàn)動(dòng)畫組功能.動(dòng)畫組對象可以加入多個(gè)動(dòng)畫對象,這些動(dòng)畫對象將會(huì)在動(dòng)畫組開始執(zhí)行的時(shí)候同時(shí)執(zhí)行動(dòng)畫.CAAnimationGroup也有自己的長度,重復(fù)次數(shù)等屬性,這些和組中的每一個(gè)動(dòng)畫屬性都不沖突.這個(gè)是很重要的,舉個(gè)例子.動(dòng)畫對象A長度1s,重復(fù)2次;動(dòng)畫對象B長度1.5s,重復(fù)2次;動(dòng)畫對象C時(shí)間長度2s,重復(fù)次數(shù)3次;動(dòng)畫組G,長度3秒,重復(fù)無數(shù)次.ABC加入G中,效果就是G在執(zhí)行一次動(dòng)畫時(shí),A可以重復(fù)執(zhí)行2次,然后空閑1s.B重復(fù)2次,沒有空閑.C執(zhí)行完第一次,在重復(fù)第二次時(shí)執(zhí)行了1秒就沒法繼續(xù)執(zhí)行了,因?yàn)?秒時(shí)間一個(gè)周期到了,G又重復(fù)了整個(gè)這個(gè)動(dòng)畫效果.
      iOS動(dòng)畫還有很多概念和屬性,由于本文要實(shí)現(xiàn)的動(dòng)畫沒有涉及到,所以就不講述了.具體的大家可以在推薦閱讀中繼續(xù)學(xué)習(xí),然后嘗試實(shí)現(xiàn)更多效果

拆分動(dòng)畫,簡化思路

不同的拆分思路,寫出來的代碼也不同,如果一開始拆分得不好,有可能代碼要多寫很多,而且還不一定能實(shí)現(xiàn).觀察一下上面提供的GIF動(dòng)畫,整個(gè)動(dòng)畫是由一個(gè)半圓弧組成,一共是三種顏色,紅黃綠色.這里有幾種思路.
  一種是整個(gè)動(dòng)畫由一個(gè)layer組成,layer上畫一個(gè)半圓弧,把圓弧分層三段,分別畫上不同的顏色.要注意的是動(dòng)畫除了圓弧的伸張縮小外還有一個(gè)平移運(yùn)動(dòng).這種思路看似簡單,但是有個(gè)地方不好實(shí)現(xiàn),就是半圓弧的拉伸和縮小,顏色長度比例要保持一樣.半圓弧可以從0畫到完整的半圓弧,再從左端向右端消失,這個(gè)容易實(shí)現(xiàn)拉伸縮小效果,但是顏色跟著拉伸縮小就不好做了.
  另一種思路是拆分動(dòng)畫,用多個(gè)layer組成,也是本文所使用的方法.通過拆分動(dòng)畫,可以簡化思路和算法.觀察GIF,可以把動(dòng)畫看成三個(gè)layer,每一個(gè)layer各自畫著自己的半圓弧,紅色保留完整弧長,微調(diào)黃色和綠色的可見弧長,其中綠色最短.注意一下,一開我也是把基礎(chǔ)圖形設(shè)定為半圓弧,但是做著做著發(fā)現(xiàn)沒法實(shí)現(xiàn)先頭紅尾綠,接著先綠尾紅這樣的效果,所以基礎(chǔ)圖形的設(shè)定是非常重要的,一定要考慮清楚,否則事倍功半.這里我們需要把基礎(chǔ)圖形設(shè)定成有兩個(gè)半圓弧連著的.如圖:


full-worm-red.png

左半圓弧可以實(shí)現(xiàn)頭紅尾綠,右半圓弧可以實(shí)現(xiàn)先綠尾紅的效果,動(dòng)畫過程中始終只顯示最多一半的路徑(半圓弧).

開始動(dòng)手

實(shí)現(xiàn)動(dòng)畫的核心部分

根據(jù)動(dòng)畫的拆分思路,先實(shí)現(xiàn)紅色部分的效果,再疊加黃色和綠色的圖層.下面是核心代碼和注釋.
CCWormView.h

//創(chuàng)建UIView的子類CCWormView
@interface CCWormView : UIView
//省略其他代碼
@end

CCWormView的實(shí)現(xiàn)

#define WORM_ANIMATION_KEY_FIRST @"WORM_ANIMATION_KEY_FIRST"
#define WORM_ANIMATION_KEY_SECOND @"WORM_ANIMATION_KEY_SECOND"
#define WORM_ANIMATION_KEY_THIRD @"WORM_ANIMATION_KEY_THIRD"
@interface CCWormView ()
//用戶實(shí)現(xiàn)紅色蟲動(dòng)畫的層
@property (nonatomic,strong) CAShapeLayer *firstWormShapeLayer;
@end
@implementation CCWormView
//省略其他代碼
//...
//初始化層
-(instancetype)initWithFrame:(CGRect)frame HUDStyle:(CCWormHUDStyle)style{
    CAShapeLayer *firstWormShapeLayer = [[CAShapeLayer alloc] init];
    //設(shè)置動(dòng)畫的路徑
    firstWormShapeLayer.path = [self wormRunLongPath];
    //畫筆寬度
    firstWormShapeLayer.lineWidth = CCWormHUDLineWidth;
    //設(shè)置線段頭尾為圓形
    firstWormShapeLayer.lineCap = kCALineCapRound;
    //設(shè)置線段拐角點(diǎn)為圓形
    firstWormShapeLayer.lineJoin = kCALineCapRound;
    //設(shè)置路徑繪制顏色為紅色
    firstWormShapeLayer.strokeColor = [UIColor redColor].CGColor;
    //設(shè)置填充顏色為透明
    firstWormShapeLayer.fillColor = [UIColor clearColor].CGColor;
    //
    firstWormShapeLayer.actions = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNull null],@"strokeStart",[NSNull null],@"strokeEnd", nil];
    //將其加入到view的layer中.
    [self.layer addSublayer:firstWormShapeLayer];    
    self.firstWormShapeLayer = firstWormShapeLayer;
    //動(dòng)畫部分,為CAShapeLayer添加動(dòng)畫效果
    [self firstWormAnimation];
}
-(CGPathRef)wormRunLongPath{
    //確定路徑起點(diǎn)位置
    CGPoint center;
    center = CGPointMake(self.frame.size.width * 9 / 10, self.frame.size.height / 2);
    //兩個(gè)連著的半圓
    CGFloat radius = (CCWormHUDViewWith / 2.0) / 2.0;
    UIBezierPath *wormPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:M_PI endAngle:2 * M_PI clockwise:YES];
    [wormPath addArcWithCenter:CGPointMake(center.x  + radius * 2, center.y) radius:radius startAngle:M_PI endAngle:2 * M_PI clockwise:YES];
    //返回CGPathRef,因?yàn)镃AShapeLayer的path屬性類型為CGPathRef
    CGPathRef path = wormPath.CGPath;
    return path;
}

下面是firstWormAnimation部分
第一步 實(shí)現(xiàn)紅色蟲運(yùn)動(dòng)的前半部分

    ///蟲子拉伸
    CABasicAnimation *strokeEndAm = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm.toValue = [NSNumber numberWithFloat:0.5];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm.fromValue = [NSNumber numberWithFloat:0];
    strokeEndAm.duration = 0.75;
    strokeEndAm.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    //動(dòng)畫結(jié)束后,保留結(jié)束時(shí)的模樣
    strokeEndAm.fillMode = kCAFillModeForwards;
    //將動(dòng)畫加入firstWormShapeLayer的代碼省略了.

將CABasicAnimation對象的動(dòng)畫關(guān)鍵路徑為strokeEnd,fromValue=0,toValue=0.5.效果就是動(dòng)畫從空開始畫,到一半路徑時(shí)結(jié)束,效果如下:

worm-strokeEndAm1.gif

第二步 實(shí)現(xiàn)紅色蟲收縮的動(dòng)畫

    //蟲子縮小
    CABasicAnimation *strokeStartAm = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm.toValue = [NSNumber numberWithFloat:0.5];
    strokeStartAm.fromValue = [NSNumber numberWithFloat:0];
    strokeStartAm.duration = 0.45;
    //如果不被Group加入的話,CACurrentMediaTime() + 0.75 才表示延遲0.75秒.
    strokeStartAm.beginTime = 0.75;//延遲一秒執(zhí)行
    strokeStartAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeStartAm.fillMode = kCAFillModeForwards;

將CABasicAnimation對象的動(dòng)畫關(guān)鍵路徑為strokeStart,fromValue=0,toValue=0.5.效果就是動(dòng)畫開始執(zhí)行時(shí)就從起點(diǎn)也就是左端慢慢消失直到一半路徑的位置停止.設(shè)置延遲0.75秒的作用就是等待蟲子拉伸動(dòng)畫結(jié)束,再執(zhí)行縮小.效果如下:
(提醒一下,下圖是單獨(dú)執(zhí)行縮小動(dòng)畫,不包括上面的拉伸動(dòng)畫的結(jié)果,由于動(dòng)畫延遲0.75秒,所以這0.75秒會(huì)顯示完整路徑,結(jié)合拉伸動(dòng)畫效果就正常了)

worm-strokeStartAm1.gif

將第一二步動(dòng)畫放入一個(gè)組里同時(shí)執(zhí)行,看看效果

    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = [NSArray arrayWithObjects: strokeEndAm, strokeStartAm, nil];
    animationGroup.repeatCount = HUGE_VALF;
    //動(dòng)畫總時(shí)間應(yīng)該以組中動(dòng)畫時(shí)間最長為標(biāo)準(zhǔn)
    animationGroup.duration = WormRunTime * 2;
    [self.firstWormShapeLayer addAnimation:animationGroup forKey:WORM_ANIMATION_KEY_FIRST];
worm-strokeEndAm1-strokeStartAm1.gif

第三步 實(shí)現(xiàn)蟲子第二階段的拉伸

    //蟲子拉伸2
    CABasicAnimation *strokeEndAm2 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm2.toValue = [NSNumber numberWithFloat:0.5 + 0.5];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm2.fromValue = [NSNumber numberWithFloat:0.5 + 0];
    strokeEndAm2.duration = 0.75;
    strokeEndAm2.beginTime = 1.2;
    strokeEndAm2.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm2.fillMode = kCAFillModeForwards;

蟲子第二階段的拉伸和第一階段基本一樣,需要改變的是toValue,fromValue的值,還有動(dòng)畫的開始時(shí)間必須是第一階段結(jié)束后立即執(zhí)行,一共是延遲1.2s,看看效果.

worm-strokeEndAm2.gif

第四步 實(shí)現(xiàn)蟲子第二階段的縮小

    //蟲子縮小2
    CABasicAnimation *strokeStartAm2 = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm2.toValue = [NSNumber numberWithFloat:0.5 + 0.5];
    strokeStartAm2.fromValue = [NSNumber numberWithFloat:0.5 + 0];
    strokeStartAm2.duration = 0.45;
    strokeStartAm2.beginTime = 0.75 + 1.2;//延遲一秒執(zhí)行
    strokeStartAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

和第一階段類似,看看效果.

worm-strokeStartAm2.gif

現(xiàn)在把前四步都放入組中同時(shí)進(jìn)行,看看效果.

    animationGroup.animations = [NSArray arrayWithObjects: strokeStartAm, strokeEndAm, strokeEndAm2, strokeStartAm2, nil];
worm-strokeEndAm1&2-strokeStartAm1&2.gif

第五步 實(shí)現(xiàn)蟲子持續(xù)移動(dòng)的效果

    //平移動(dòng)畫
    CABasicAnimation *xTranslationAm = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm.toValue = [NSNumber numberWithFloat: (40 / -1.0)];
    xTranslationAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm.duration = 1.18;
    xTranslationAm.fillMode = kCAFillModeForwards;
    
    CABasicAnimation *xTranslationAm2 = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm2.toValue = [NSNumber numberWithFloat: (40 / -1.0) * 2];
    xTranslationAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm2.duration = 1.18;
    xTranslationAm2.beginTime = 1.20;
    xTranslationAm2.fillMode = kCAFillModeForwards;

持續(xù)移動(dòng)可以沿x軸平移來實(shí)現(xiàn),平移要分兩部分,第一階段移動(dòng)結(jié)束后稍微停留一下,再繼續(xù)第二階段的移動(dòng).每一個(gè)階段的移動(dòng)停留時(shí)間和蟲子拉伸縮小總時(shí)長一樣.將平移和上面四步動(dòng)畫結(jié)合,看看效果

worm-strokeEndAm1&2-strokeStartAm1&2-translation.x.gif

這樣,紅色蟲的動(dòng)畫效果基本就實(shí)現(xiàn)了.

整合動(dòng)畫實(shí)現(xiàn)最終效果

現(xiàn)在還差黃色和綠色部分的動(dòng)畫.那么怎么實(shí)現(xiàn)黃色部分?注意觀察,如果蟲子在拉伸還沒結(jié)束的時(shí)候就開始縮小,是不是效果就是黃色那樣!如果黃色拉伸時(shí)間比紅色慢,其他時(shí)間比例都不變,是不是可以實(shí)現(xiàn)紅色先走隨后拉動(dòng)黃色呢?答案是肯定的.
  黃色和綠色的動(dòng)畫邏輯和紅色是一模一樣的,不同的就是動(dòng)畫的參數(shù)設(shè)置不同,下面給出黃色和綠色的參數(shù).
先看看黃色蟲子的參數(shù)設(shè)置.

    //蟲子拉伸
    CABasicAnimation *strokeEndAm = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm.toValue = [NSNumber numberWithFloat:0.5];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm.fromValue = [NSNumber numberWithFloat:0.010];
    strokeEndAm.duration = 0.75;
    strokeEndAm.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm.fillMode = kCAFillModeForwards;
    
    //蟲子縮小
    CABasicAnimation *strokeStartAm = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm.toValue = [NSNumber numberWithFloat:0.490];
    strokeStartAm.fromValue = [NSNumber numberWithFloat:0];
    strokeStartAm.duration = 0.70;
    strokeStartAm.beginTime = 0.50;
    strokeStartAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeStartAm.fillMode = kCAFillModeForwards;
    
    
    //蟲子拉伸2 拉伸的第二階段,必須讓上一層的第二階段先動(dòng)
    CABasicAnimation *strokeEndAm2 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm2.toValue = [NSNumber numberWithFloat:0.5 + 0.5];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm2.fromValue = [NSNumber numberWithFloat:0.5];
    strokeEndAm2.duration = 0.75;
    strokeEndAm2.beginTime = 1.2 + 0.15;
    strokeEndAm2.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm2.fillMode = kCAFillModeForwards;
    
    //蟲子縮小2
    CABasicAnimation *strokeStartAm2 = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm2.toValue = [NSNumber numberWithFloat:0.5 + 0.5];
    strokeStartAm2.fromValue = [NSNumber numberWithFloat:0.5 + 0];
    strokeStartAm2.duration = 0.30;
    strokeStartAm2.beginTime =0.15 + 0.75 + 1.2;
    strokeStartAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    
    
    //平移動(dòng)畫
    CABasicAnimation *xTranslationAm = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm.toValue = [NSNumber numberWithFloat: (40 / -1.0)];
    xTranslationAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm.duration = 1.18;
    xTranslationAm.fillMode = kCAFillModeForwards;
    
    CABasicAnimation *xTranslationAm2 = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm2.toValue = [NSNumber numberWithFloat: (40 / -1.0) * 2];
    xTranslationAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm2.duration = 1.18;
    xTranslationAm2.beginTime = 1.20;
    xTranslationAm2.fillMode = kCAFillModeForwards;
    
    
    
    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = [NSArray arrayWithObjects: strokeStartAm, strokeEndAm, xTranslationAm, strokeEndAm2, strokeStartAm2, xTranslationAm2, nil];
    animationGroup.repeatCount = HUGE_VALF;
    //動(dòng)畫總時(shí)間應(yīng)該以組中動(dòng)畫時(shí)間最長為標(biāo)準(zhǔn)
    animationGroup.duration = 1.2 * 2;
    [self.secondWormShapeLayer addAnimation:animationGroup forKey:nil];

將黃色和紅色兩個(gè)層合并在一起同時(shí)執(zhí)行動(dòng)畫,看看效果

worm-first&secondLayer.gif

下面是綠色蟲子的參數(shù).

    //蟲子拉伸
    CABasicAnimation *strokeEndAm = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm.toValue = [NSNumber numberWithFloat:0.5];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm.fromValue = [NSNumber numberWithFloat:0.010];
    strokeEndAm.duration = 0.75;
    strokeEndAm.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm.fillMode = kCAFillModeForwards;
    
    //蟲子縮小
    CABasicAnimation *strokeStartAm = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm.toValue = [NSNumber numberWithFloat:0.490 ];
    strokeStartAm.fromValue = [NSNumber numberWithFloat:0];
    strokeStartAm.duration = 0.90;
    strokeStartAm.beginTime = 0.25;
    strokeStartAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeStartAm.fillMode = kCAFillModeForwards;
    
    
    //蟲子拉伸2 拉伸的第二階段,必須讓上一層的第二階段先動(dòng)
    CABasicAnimation *strokeEndAm2 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm2.toValue = [NSNumber numberWithFloat:0.5 + 0.5];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm2.fromValue = [NSNumber numberWithFloat:0.5];
    strokeEndAm2.duration = 0.75;
    strokeEndAm2.beginTime = 1.2 + 0.15 + 0.20;
    strokeEndAm2.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm2.fillMode = kCAFillModeForwards;
    
    //蟲子縮小2
    CABasicAnimation *strokeStartAm2 = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm2.toValue = [NSNumber numberWithFloat:0.5 + 0.5];
    strokeStartAm2.fromValue = [NSNumber numberWithFloat:0.5 + 0];
    strokeStartAm2.duration = 0.30;
    strokeStartAm2.beginTime =0.15 + 0.75 + 1.2;
    strokeStartAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    
    
    //平移動(dòng)畫
    CABasicAnimation *xTranslationAm = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm.toValue = [NSNumber numberWithFloat: (HUDWith / -1.0 + 10)];
    xTranslationAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm.duration = 1.18;
    xTranslationAm.fillMode = kCAFillModeForwards;
    
    CABasicAnimation *xTranslationAm2 = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm2.toValue = [NSNumber numberWithFloat: (HUDWith / -1.0 + 10) * 2];
    xTranslationAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm2.duration = 1.18;
    xTranslationAm2.beginTime = 1.20;
    xTranslationAm2.fillMode = kCAFillModeForwards;
    
    
    
    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = [NSArray arrayWithObjects: strokeStartAm, strokeEndAm, xTranslationAm, strokeEndAm2, strokeStartAm2, xTranslationAm2, nil];
    animationGroup.repeatCount = HUGE_VALF;
    //動(dòng)畫總時(shí)間應(yīng)該以組中動(dòng)畫時(shí)間最長為標(biāo)準(zhǔn)
    animationGroup.duration = 1.2 * 2;
    [self.thirdWormShapeLayer addAnimation:animationGroup forKey:nil];

最終將紅黃綠三個(gè)蟲子合并,看看合成的效果

worm-first&second&thirdLayer.gif

  .

優(yōu)化代碼,實(shí)現(xiàn)HUD的功能

三個(gè)蟲子的動(dòng)畫邏輯都是一下的,所以這里可以優(yōu)化一下代碼結(jié)構(gòu),最后加上UIView動(dòng)畫,實(shí)現(xiàn)一個(gè)指示器(HUD),這樣就可以提供給別人使用了~
  先把蟲子動(dòng)畫中變化的參數(shù)提取出來,封裝在一個(gè)方法里,然后三個(gè)蟲分別給出自己的參數(shù)調(diào)用一下方法就可以得到動(dòng)畫對象了.封裝后的代碼如下.(其中有一些是全局靜態(tài)變量和宏定義,不用太在意,具體可參考Git上的完整源碼)


/**
 *  第三條蟲子嚅動(dòng)
 */
-(void)thirdWormAnimation{
    
    CAAnimationGroup *animationGroup = [self baseWormAnimationWithEnd1From:0.010 end1To:0.5 end1Duration:0.75 start1From:0 start1To:0.490 start1Duration:0.9 start1Begin:0.25 end2From:0.5 end2To:0.5 + 0.5 end2Duration:0.75 end2Begin:WormRunTime + 0.15 + 0.20 start2From:0.5 start2To:0.5 + 0.5 start2Duration:0.30 start2Begin:0.15 + 0.75 + WormRunTime];
    
    [self.thirdWormShapeLayer addAnimation:animationGroup forKey:WORM_ANIMATION_KEY_THIRD];
}

/**
 *  第二條蟲子嚅動(dòng)
 */
-(void)secondWormAnimation{
    
    CAAnimationGroup *animationGroup = [self baseWormAnimationWithEnd1From:0.010 end1To:0.5 end1Duration:0.75 start1From:0 start1To:0.490 start1Duration:0.70 start1Begin:0.50 end2From:0.5 end2To:0.5 + 0.5 end2Duration:0.75 end2Begin:WormRunTime + 0.15 start2From:0.5 start2To:0.5 + 0.5 start2Duration:0.30 start2Begin:0.15 + 0.75 + WormRunTime];
    
    [self.secondWormShapeLayer addAnimation:animationGroup forKey:WORM_ANIMATION_KEY_SECOND];
}

/**
 *  第一條蟲子嚅動(dòng) (最底部的那條)
 */
-(void)firstWormAnimation{
    CAAnimationGroup *animationGroup = [self baseWormAnimationWithEnd1From:0 end1To:0.5 end1Duration:0.75 start1From:0 start1To:0.5 start1Duration:0.45 start1Begin:0.75 end2From:0.5 end2To:0.5 + 0.5 end2Duration:0.75 end2Begin:1.2 start2From:0.5 start2To:0.5 + 0.5 start2Duration:0.45 start2Begin:0.75 + WormRunTime];
    
    [self.firstWormShapeLayer addAnimation:animationGroup forKey:WORM_ANIMATION_KEY_FIRST];
}

-(CAAnimationGroup *)baseWormAnimationWithEnd1From:(CGFloat)end1FromValue end1To:(CGFloat)end1ToValue end1Duration:(CGFloat)end1Duration start1From:(CGFloat)start1FromValue start1To:(CGFloat)start1ToValue start1Duration:(CGFloat)start1Duration start1Begin:(CGFloat)start1BeginTime end2From:(CGFloat)end2FromValue end2To:(CGFloat)end2ToValue end2Duration:(CGFloat)end2Duration end2Begin:(CGFloat)end2BeginTime start2From:(CGFloat)start2FromValue start2To:(CGFloat)start2ToValue start2Duration:(CGFloat)start2Duration start2Begin:(CGFloat)start2BeginTime{
    
    
    //蟲子拉伸1
    CABasicAnimation *strokeEndAm = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm.toValue = [NSNumber numberWithFloat:end1ToValue];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm.fromValue = [NSNumber numberWithFloat:end1FromValue];
    strokeEndAm.duration = end1Duration;
    strokeEndAm.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm.fillMode = kCAFillModeForwards;
    
    //蟲子縮小1
    CABasicAnimation *strokeStartAm = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm.toValue = [NSNumber numberWithFloat:start1ToValue];
    strokeStartAm.fromValue = [NSNumber numberWithFloat:start1FromValue];
    strokeStartAm.duration = start1Duration;
    //如果不被Group加入的話,CACurrentMediaTime() + 1 才表示延遲1秒.
    strokeStartAm.beginTime = start1BeginTime;//延遲執(zhí)行
    strokeStartAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeStartAm.fillMode = kCAFillModeForwards;
    
    
    //蟲子拉伸2 拉伸的第二階段,必須讓上一層的第二階段先動(dòng)
    CABasicAnimation *strokeEndAm2 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAm2.toValue = [NSNumber numberWithFloat:end2ToValue];
    //fromValue 開始畫時(shí)已經(jīng)存在的部分的量
    strokeEndAm2.fromValue = [NSNumber numberWithFloat:end2FromValue];
    strokeEndAm2.duration = end2Duration;
    strokeEndAm2.beginTime = end2BeginTime;
    strokeEndAm2.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :0.55];
    strokeEndAm2.fillMode = kCAFillModeForwards;
    
    //蟲子縮小2
    CABasicAnimation *strokeStartAm2 = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAm2.toValue = [NSNumber numberWithFloat:start2ToValue];
    strokeStartAm2.fromValue = [NSNumber numberWithFloat:start2FromValue];
    strokeStartAm2.duration = start2Duration;
    //如果不被Group加入的話,CACurrentMediaTime() + 1 才表示延遲1秒.
    strokeStartAm2.beginTime = start2BeginTime;//延遲一秒執(zhí)行
    strokeStartAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    
    
    //平移動(dòng)畫
    CABasicAnimation *xTranslationAm = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm.toValue = [NSNumber numberWithFloat: ( (CCWormHUDViewWith / 2.0) / -1.0)];
    xTranslationAm.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm.duration = 1.18;
    xTranslationAm.fillMode = kCAFillModeForwards;
    
    CABasicAnimation *xTranslationAm2 = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    xTranslationAm2.toValue = [NSNumber numberWithFloat: ( (CCWormHUDViewWith / 2.0) / -1.0) * 2];
    xTranslationAm2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    xTranslationAm2.duration = 1.18;
    xTranslationAm2.beginTime = 1.20;
    xTranslationAm2.fillMode = kCAFillModeForwards;
    
    
    
    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = [NSArray arrayWithObjects: strokeStartAm, strokeEndAm, xTranslationAm, strokeEndAm2, strokeStartAm2, xTranslationAm2, nil];
    animationGroup.repeatCount = HUGE_VALF;
    //動(dòng)畫總時(shí)間應(yīng)該以組中動(dòng)畫時(shí)間最長為標(biāo)準(zhǔn)
    animationGroup.duration = WormRunTime * 2;
    
    return animationGroup;
}

接下來為這個(gè)動(dòng)畫所在的視圖添加UIView級(jí)別動(dòng)畫,做成一個(gè)HUD,并且提供兩個(gè)公開的實(shí)例方法用于顯示和隱藏HUD
  講了半天Core Animation動(dòng)畫,不知道你是不是累了.往下看,接下來你一定會(huì)覺得使用UIView級(jí)別的動(dòng)畫是多么輕松,讓我們開始輕松一下.下面看一下怎么在顯示和隱藏視圖時(shí)使用動(dòng)畫.

-(void)startLodingWormHUD{
    self.isShowing = YES;
    [self.presentingView addSubview:self];
    //動(dòng)起來.顯示指示器的時(shí)候才開始設(shè)置動(dòng)畫效果
    [self firstWormAnimation];
    [self secondWormAnimation];
    [self thirdWormAnimation];
    
    self.transform = CGAffineTransformMakeScale(0.1, 0.1);
    [UIView animateKeyframesWithDuration:0.6 delay:0.0 options:0 animations:^{
        [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.5 animations:^{
            self.transform = CGAffineTransformMakeScale(1.3, 1.3);
        }];
        [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
            self.transform = CGAffineTransformIdentity;
        }];
    } completion: nil];
}

-(void)endLodingWormHUD{
    //隱藏指示器,同時(shí)移除動(dòng)畫
    [UIView animateKeyframesWithDuration:0.6 delay:0 options:0 animations:^{
        [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.5 animations:^{
            self.transform = CGAffineTransformMakeScale(1.2, 1.2);
        }];
        [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
            self.transform = CGAffineTransformMakeScale(0.1, 0.1);
        }];
        
    } completion:^(BOOL finished){
        self.isShowing = NO;
        [self.firstWormShapeLayer removeAnimationForKey:WORM_ANIMATION_KEY_FIRST];
        [self.secondWormShapeLayer removeAnimationForKey:WORM_ANIMATION_KEY_SECOND];
        [self.thirdWormShapeLayer removeAnimationForKey:WORM_ANIMATION_KEY_THIRD];
        [self removeFromSuperview];
    }];
}

如果在此之前你還不了解UIView動(dòng)畫的基本變化,可參考理解iOS中CGAffineTransform與矩陣的關(guān)系

最后看一下結(jié)合指示器后勤奮蟲子的最終效果

a-diligent-worm-in-iOS.gif

后語

從一開始接觸動(dòng)畫到實(shí)現(xiàn)一個(gè)完整的動(dòng)畫中間,肯定會(huì)經(jīng)歷很多困惑與不解,然后就要不停搜索查文檔了,這個(gè)過程雖然很累,但是一旦你把效果實(shí)現(xiàn)了,原理想明白了,那是多么享受的一件事情.這里我給一個(gè)建議,由于iOS動(dòng)畫中有太多類和屬性,方法,你不可能一下子就把他們都明白透徹.一開始肯定是要先找準(zhǔn)幾個(gè)基本的概念先理解了,然后嘗試動(dòng)手寫demo,實(shí)現(xiàn)一些基礎(chǔ)效果.再慢慢將他們結(jié)合起來朝著你的目標(biāo)效果前進(jìn).如果中途發(fā)現(xiàn)走不動(dòng)了,效果實(shí)現(xiàn)很久也不能完成,那很可能是少用了某些對象,屬性,方法,或者你的基礎(chǔ)圖形都是錯(cuò)的.比如本文,一開始沒有使用動(dòng)畫組,發(fā)現(xiàn)很多效果做不出,然后我查了一下文檔資料發(fā)現(xiàn)可以使用動(dòng)畫組實(shí)現(xiàn),效果很容易就出來了.基礎(chǔ)圖形也是,從一個(gè)半圓弧到兩個(gè)半圓弧,實(shí)現(xiàn)起來事半功倍.這個(gè)過程需要時(shí)間和經(jīng)驗(yàn),慢慢來吧,一起進(jìn)步!
  至此,勤奮蟲子的完整效果都已經(jīng)實(shí)現(xiàn)了.還有沒多具體細(xì)節(jié)上面并沒有提到.本文所實(shí)現(xiàn)的指示器源碼和使用Demo可以從本文源碼:GitHub中找到,可以的話幫忙點(diǎn)一下Star,好讓更多人看到.
  希望大家能從本文中得到自己需要的東西,如果你有更好的想法或發(fā)現(xiàn)本文的不足,歡迎指出來.更多文章我還來不及發(fā)到簡書上,可在我的博客查看

參考閱讀

本文源碼:GitHub
iOS開發(fā)系列--讓你的應(yīng)用“動(dòng)”起來
按鈕打勾動(dòng)畫特效
理解iOS中CGAffineTransform與矩陣的關(guān)系
下面是工具
CAMediaTimingFunction playground
Sketch下載 密碼:5d2b
PaintCode下載 密碼:ftpt

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

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,692評論 4 61
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,698評論 6 30
  • 在中國自由知識(shí)分子階層里面,吳曉波無疑是優(yōu)秀代表。我以前陸續(xù)看過他的幾本書,比如《大敗局》,《激蕩三十年》。。。那...
    愛吃香蕉的猴閱讀 257評論 1 2
  • 第七篇 時(shí)間不早了,但還得忙著交作業(yè),有點(diǎn)小小的為難,但依然拿起手機(jī)隨意寫。 今天的主題是占星,我不太熟悉,但知道...
    正念此心閱讀 353評論 0 1
  • 看了加勒比海盜5,好好看。買了屈臣氏漱口水,試試效果。吃了今年夏天第一個(gè)冰激凌和雪碧,爽爆了哈哈哈。明天大掃除,扔...
    明天你好郭郭閱讀 286評論 0 0

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