淺談Layer和Animation

iOS中的動畫默認是指Core Animation,當然還有第三方的比如Facebook的Pop等。Core Animation是作用在圖層Layer上的,所以本文分別介紹LayerAnimation

Layer 與 View

View與Layer關(guān)系

在iOS中,每一個UIView背后都有一個Layer,這個我們可以通過view.layer獲得。而ViewLayerdelegate。這個delegate是這樣定義的:

@interface NSObject (CALayerDelegate)
...

/* If defined, called by the default implementation of the
 * -actionForKey: method. Should return an object implementating the
 * CAAction protocol. May return 'nil' if the delegate doesn't specify
 * a behavior for the current event. Returning the null object (i.e.
 * '[NSNull null]') explicitly forces no further search. (I.e. the
 * +defaultActionForKey: method will not be called.) */

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

@end

Layer是真正做顯示和動畫的,而View是一種高級封裝,并提供用戶交互功能。

**Q: **既然每個View都有一個Layer,為什么要分開成兩種對象呢,為什么不把這些功能全部放到View本身去?
A: 這是蘋果為了跨平臺考慮的。 因為iOS和Mac OS 對用戶的交互處理是有很大區(qū)別的,一個是多點觸控,一個是鍵盤鼠標。而兩者對于界面元素顯示和動畫處理確是相似的。這樣,將Layer分離出來可以起到職責分離、代碼復(fù)用的作用,同時也方便第三方庫的開發(fā)者。

大家也許注意到了,圖中的Layer標記為Root Layer。我們可以把View本身攜帶的既創(chuàng)建View時創(chuàng)建的Layer稱為Root Layer,相反,把那些單獨的Layer稱為非Root Layer

Q: Root Layer 和 非 Root Layer有什么區(qū)別?
A: 改變一個非Root Layer的可做動畫屬性(Animatable Property)時,屬性值從起點到終點有一個平滑過渡的過程,既隱式動畫,默認時長是0.25秒。而改變一個Root Layer的可做動畫屬性時,是直接改變的,沒有動畫的。我們可以用下面的代碼演示改變兩種layer的顏色。

- (void)changeColor {
    [CATransaction begin];
    //為了方便觀察,將時長改為2秒
    [CATransaction setAnimationDuration:2.0];
    
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    //改變 非Root Layer的背景色 會有隱式動畫
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    //改變 Root Layer的背景色 沒有隱式動畫
    self.colorView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

    [CATransaction commit];
}
改變顏色動畫1

當然,要想讓Root Layer改變顏色時有動畫也是辦法的,我們只需要把它放在一個block中。

//在block中, 改變 Root Layer的背景色 會有隱式動畫
[UIView animateWithDuration:2.0 animations:^{
  self.colorView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}]; 
改變顏色動畫 2

這又是為什么呢?

其實,官方文檔已經(jīng)對此有簡單的說明。

The UIView Class disables layer animation by default but reenables them inside animation blocks.

繼續(xù)死磕,會發(fā)現(xiàn)原因跟上面提到的CALayerDelegate里面的actionForLayer:forKey方法有關(guān)。這個方法有三種返回結(jié)果:

  1. 返回非空值,既某種行為。這樣就是動畫效果。
  1. 返回nil,不做什么行為,繼續(xù)去其他地方尋找合適的actions。
  2. 返回Null,停止尋找。

至此,我們知道了根因,就是默認情況下,UIview的actionForLayer:forKey方法返回nil。而在block中時,返回一個非空值。

Layer Tree

圖層樹狀結(jié)構(gòu)以及對應(yīng)的視圖層級

每一個視圖都有一個父視圖以及若干個子視圖,這形成了一個樹狀的層級關(guān)系。對應(yīng)地,每個視圖的圖層也有一個平行的層級關(guān)系,稱之為圖層樹(Layer Tree)。直接創(chuàng)建的或者通過UIView獲得的(view.layer)用于顯示的圖層樹,稱之為模型樹(Model Tree),模型樹的背后還存在兩份圖層樹的拷貝,一個是呈現(xiàn)樹(Presentation Tree),一個是渲染樹(Render Tree)

模型樹則可以通過modelLayer屬性獲得,而呈現(xiàn)樹可以通過模型樹的layer.presentationLayer獲得。模型樹的屬性值就是我們看到的動畫起始和結(jié)束時的值,是靜態(tài)的;呈現(xiàn)樹的屬性值和動畫運行過程中界面上看到的是一致的,是動態(tài)的。而渲染樹是私有的,你無法訪問到,渲染樹是對呈現(xiàn)樹的數(shù)據(jù)進行渲染,為了不阻塞主線程,渲染的過程是在單獨的進程或線程中進行的,所以你會發(fā)現(xiàn)Animation的動畫并不會阻塞主線程。

Layer Property

Layer有很多屬性,這里強調(diào)兩個屬性:

anchorPoint

錨點是按照layer的bounds比例取值的,其值是左上角(0,0)到右下角(1,1),默認是(0.5, 0.5)既中心點。形象地,我們可以認為是動畫(平移、縮放、旋轉(zhuǎn))的支點。
下面的動畫演示了使用默認的錨點(0.5,0.5)和(0,1)的區(qū)別。

默認錨點(0.5,0.5),鉛筆的中心點在路徑上移動
錨點改為(0,1.0),鉛筆的筆尖在路徑上移動

position

position有點類似于UIView的center,但不總是中心點,它是anchorPoint相對于父layer的位置。所以layer的frame不變時,改變anchorPoint也會改變position的值;同樣,position不變時,改變anchorPoint值也會改變frame的origin的值。

anchorPoint與position關(guān)系

Animations

動畫有隱式動畫顯式動畫之分。前面我們以及介紹了隱式動畫的原理了,接下來主要講顯式動畫
顯式動畫就是我們在layer上調(diào)用了addAnimation:forKey方法。這里一旦一個layer添加了一個動畫時,就拷貝了一份Animation對象,所以接下來的對Animation對象的修改只會對后面添加的layer起作用。

動畫的基類是CAAnimation,它與各派生類的關(guān)系見下圖:

CAAnimation 繼承關(guān)系

我們看一個簡單的CABasicAnimation的例子,平移一個圓塊。

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @0;
animation.toValue = @200;
animation.duration = 1;
[circle.layer animation forKey:@"basic"];
basicAnimation

我們發(fā)現(xiàn),動畫結(jié)束后立馬回到原點。 這就牽扯到我們前面提到的Model TreePresentation Tree了。

圓塊移動過程中,改變的是Presentation Tree的layer屬性值,而Model Tree的layer值沒變,動畫結(jié)束時默認是刪除的,所以又變回Model Tree的layer屬性值了,既回到原點。解決辦法有兩個:

  • 動畫結(jié)束后,手動修改Model Tree的layer屬性值
circle.layer.position = CGPointMake(200, 220);
  • 動畫結(jié)束時不刪除
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;

兩種方案都可以使圓塊保持在最末端,但推薦第一種,因為第二種沒有刪除動畫,會浪費渲染資源,而且也造成Model TreePresentation Tree不同步。

時間系統(tǒng)

CAMediaTiming是一個協(xié)議,控制了動畫運行時間相關(guān)的系數(shù)。CALayerCAAnimtion都實現(xiàn)了這個協(xié)議。它有一些重要的參數(shù)。

  • beginTime
    動畫開始的延遲時間,相對于父layer的時間。一般取值為
   layer.beginTime = CACurrentMediaTime() + 延遲的秒數(shù)
  • speed
    動畫執(zhí)行的速度,有疊加效果。比如layer的速度是2,父layer的速度是2,那這個layer上動畫執(zhí)行的速度就是4。speed還可以是負值,這會導(dǎo)致動畫反向執(zhí)行。

  • repeatCount

動畫執(zhí)行的次數(shù),可以為小數(shù),0.5代表動畫執(zhí)行一般就結(jié)束。

  • repeatDuration

動畫重復(fù)的時長,可以比duration小,那就中途結(jié)束。

  • timeOffset

可以把動畫時間想象成一個圓環(huán),從中間一個位置開始執(zhí)行,到結(jié)尾再循環(huán)執(zhí)行到剛剛開始的地方。

  • autoreverses

為True時,動畫再反向執(zhí)行一遍。

  • fillMode

動畫開始之前或者結(jié)束之后的填充行為,默認是kCAFillModeRemoved。前面用到的kCAFillModeForwards是動畫結(jié)束之后保持最后狀態(tài),kCAFillModeBackwards是動畫開始之前就保持最開始的狀態(tài)。

下圖可以清晰地看出各個參數(shù)的含義,動畫演示的是從橘色變成藍色的過程,橫向帶變動畫時刻。

CAMediaTiming 參數(shù)行為

特別指出,這里的speed為0代表動畫暫停,與timeOffset一起可以暫停/恢復(fù) 動畫。

- (void)pauseAnimation:(CALayer *)layer {
    CFTimeInterval pauseTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0;
    layer.timeOffset = pauseTime;
}

- (void)resumeAnimation:(CALayer *)layer {
    CFTimeInterval pauseTime = [layer timeOffset];
    layer.speed = 1;
    layer.timeOffset = 0;
    layer.beginTime = 0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pauseTime;
    layer.beginTime = timeSincePause;
}
暫停/恢復(fù)動畫

CAMediaTimingFunction

用于計算起點與終點之間的插值,控制動畫的節(jié)奏,基本有四種,起變換節(jié)奏曲線如下圖:

CAMediaTimingFunction

POP

POP是facebook開源獨立于CoreAnimation的動畫方案。與CoreAnimation的區(qū)別主要是:

1.POP 在動畫的任意時刻,可以保持Model Layer與 Presentation Layer同步,CoreAnimation做不到。

  1. POP可以應(yīng)用于任意NSObject對象,CoreAnimation只能應(yīng)用于CALayer。

基本的POP動畫有

  • POPBasicAnimation
    用法類似于CABasicAnimation。
  • POPSpringAnimation
    有彈簧效果,節(jié)奏曲線如下圖:
POPSpringAnimation節(jié)奏曲線

可以用springSpeed,springBounciness等控制彈簧的效果。

  • POPDecayAnimation
    衰減效果,常見于ScrollView滑動時停止的衰減效果。

  • POPCustomAnimation
    自定義動畫。

Shimmer

Shimmer也是facebook出品的實現(xiàn)閃動效果的動畫,iPhone滑動解鎖的效果就可以用這個實現(xiàn)。

shimmerView.contentView = shimmerLabel;
shimmerView.shimmeringOpacity = 0.1;
shimmerView.shimmeringAnimationOpacity = 1.0;
shimmerView.shimmeringBeginFadeDuration = 0.3;
shimmerView.shimmering = YES;
Shimmer動畫

其原理也很簡單,就是添加contentView 作為subView, 然后創(chuàng)建一個CAGradientLayer 作為contentView.layer的mask。移動gradientLayer就可以有這個效果。

參考文章

ios core animation advanced techniques
obj.io
controlling-animation-timing
POP 介紹與實踐
談?wù)刬OS Animation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容