ios动画原理 modelLayer和presentationLayer以及点击交互
ios動畫原理 modelLayer和presentationLayer以及點擊交互
我們知道,iOS的動畫,和其對應的layer有關。
之前在開發的過程中碰到一個問題,那就是,在一個視圖的動畫過程中,這個視圖view和Layer的frame是怎么變化的?
1 動畫過程中frame的變化
為了研究動畫過程中,view和Layer的frame變化,做了簡單的動畫打印測試,效果如下:
由結果可知,在UIView動畫的回調中添加打印,只會打印一次,且打印的是最終的view和Layer 的位置,這顯然不是想要的結果,所以,在動畫的回調block中打印是不能實現的;
1.1 添加KVO監聽frame的變化
在動畫之前,對視圖view和layer分別添加kvo監聽其frame的變化,看是否能在動畫過程中,視圖的frame發生變化,從而打印出對應的frame:
如上圖打印結果,添加kvo監聽,對應的view和layer,只監聽到了修改view的一次frame的變化,添加斷點,監聽的回調也只走一次;
??問題:
1.view的frame變化為什么只有一次?動畫的過程中,view的frame不變?
2.為什么監聽不到layer 的frame的變化? 最終通過第一次的實驗,layer的frame是發生了變化的啊?
1.2 添加CADisplayLink 打印frame的變化
帶著1.1中的兩個問題,回想到view動畫的實現原理,動畫的過程其實是一幀一幀顯示的,所以,每一幀對應的view或者layer的位置是不斷變化的;
所以應該嘗試打印在每一幀的顯示時,view和layer的frame;
而這恰好可以使用系統自帶的CADisplayLink,它的回調頻率,和設備的刷幀是一致的,添加測試如下:
注意:添加一個dispatch_after,是為了避免一直打印,不方便查看打印結果,沒有其他作用;
打印發現,view和layer的frame還是每次打印的最終位置的frame;
由此,證明了動畫過程
view的frame只改變的一次,直接改到了最終的frame;
而動畫的view的layer,確實是和動畫相關的,難道它的frame也只改變了一次?
2 關于modelLayer和presentationLayer
打開CALayer的頭文件,發現了其下兩個屬性:
/* Returns a copy of the layer containing all properties as they were* at the start of the current transaction, with any active animations* applied. This gives a close approximation to the version of the layer* that is currently displayed. Returns nil if the layer has not yet* been committed.** The effect of attempting to modify the returned layer in any way is* undefined.** The `sublayers', `mask' and `superlayer' properties of the returned* layer return the presentation versions of these properties. This* carries through to read-only layer methods. E.g., calling -hitTest:* on the result of the -presentationLayer will query the presentation* values of the layer tree. *// *在-presentationLayer方法的結果上調用時,返回*具有當前模型值的基礎層。當調用*非表示層,返回接收者。通話結果*產生展示的交易后的此方法*圖層已完成是不確定的。 * // *返回包含所有屬性的圖層的副本*在當前交易開始時,帶有任何活動的動畫*適用。這非常接近該圖層的版本*當前顯示。如果圖層尚未返回nil*已承諾。**嘗試以任何方式修改返回的圖層的效果是*未定義。**返回的`sublayers',`mask'和`superlayer'屬性*層返回這些屬性的表示形式。這個*進行只讀層方法。例如,調用-hitTest:* -presentationLayer的結果將查詢演示文稿*層樹的值。 * /- (nullable instancetype)presentationLayer;/* When called on the result of the -presentationLayer method, returns* the underlying layer with the current model values. When called on a* non-presentation layer, returns the receiver. The result of calling* this method after the transaction that produced the presentation* layer has completed is undefined. *// *在-presentationLayer方法的結果上調用時,返回*具有當前模型值的基礎層。當調用*非表示層,返回接收者。通話結果*產生展示的交易后的此方法*圖層已完成是不確定的。 * /- (instancetype)modelLayer;將回調中的layer,換成了presentationLayer,打印結果如下:
打印發現了一開始想要的結果,原來直接打印view的frame之變化一次是對的,真正的發生動畫的是一個叫presentationLayer的東西;
動畫的整個過程其實經歷了三個樹狀結構,才顯示到了屏幕上:模型樹–>呈現樹–>渲染樹,如圖:
通常,我們操作的是模型樹;
在重繪周期最后,我們會將模型樹相關內容(層次結構、圖層屬性和動畫)序列化,通過IPC傳遞給專門負責屏幕渲染的渲染進程。渲染進程拿到數據并反序列化出樹狀結構–呈現樹。
這個呈現圖層實際上是模型圖層的復制,但是它的屬性值代表了在任何指定時刻當前外觀效果。換句話說,你可以通過呈現圖層的值來獲取當前屏幕上真正顯示出來的值。
我們可以通過CALayer的presentationLayer方法來訪問對應的呈現樹圖層。
注意:呈現圖層僅僅當圖層首次被提交(就是首次第一次在屏幕上顯示)的時候創建,所以在那之前調用-presentationLayer將會返回nil。
–modelLayer方法: 在呈現圖層上調用–modelLayer將會返回它正在呈現所依賴的CALayer。通常在一個圖層上調用-modelLayer會返回–self(實際上我們已經創建的原始圖層就是一種數據模型)。
一個移動的圖層是如何通過數據模型呈現的:
大多數情況下,你不需要直接訪問呈現圖層,你可以通過和模型圖層的交互,來讓Core Animation更新顯示。
兩種情況下presentationLayer呈現圖層會變得很有用:
- 同步動畫,
- 動畫過程中處理用戶交互。
當模型樹上帶有動畫特征時,提交到渲染進程后,渲染進程會根據動畫特征,不斷修改呈現樹上的圖層屬性,并同時不斷的在屏幕上渲染出來,這樣我們就看到了動畫。
2.1 模型樹與呈現樹關系的比喻
在CALayer內部,它控制著兩個屬性:presentationLayer(以下稱為P)和modelLayer(以下稱為M)。
P只負責顯示,M只負責數據的存儲和獲取。
我們對layer的各種屬性賦值比如frame,實際上是直接對M的屬性賦值;
而P將在每一次屏幕刷新的時候回到M的狀態。
比如此時M的狀態是1,P的狀態也是1,然后我們把M的狀態改為2,那么此時P還沒有過去,也就是我們看到的狀態P還是1,在下一次屏幕刷新的時候P才變為2。而我們幾乎感知不到兩次屏幕刷新之間的間隙,所以感覺就是我們一對M賦值,P就過去了。
P就像是瞎子,M就像是瘸子,瞎子背著瘸子,瞎子每走一步(也就是每次屏幕刷新的時候)都要去問瘸子應該怎樣走(這里的走路就是繪制內容到屏幕上),瘸子沒法走,只能指揮瞎子背著自己走。
重點: 動畫完成回到原地
可以簡單的理解為:一般情況下,任意時刻P都會回到M的狀態。
而當一個CAAnimation(以下稱為A)加到了layer上面后,A就把M從P身上擠下去了。
現在P背著的是A,P同樣在每次屏幕刷新的時候去問他背著的那個家伙,A就指揮它從fromValue到toValue來改變值。而動畫結束后,A會自動被移除,這時P沒有了指揮,就只能大喊“M你在哪”,M說我還在原地沒動呢,于是P就順聲回到M的位置了。
這就是為什么動畫結束后我們看到這個視圖又回到了原來的位置,是因為我們看到在移動的是P,而指揮它移動的是A,M永遠停在原來的位置沒有動,動畫結束后A被移除,P就回到了M的懷里。
動畫結束后,P會回到M的狀態(當然這是有前提的,因為動畫已經被移除了,我們可以設置fillMode來繼續影響P),但是這通常都不是我們動畫想要的效果。我們通常想要的是,動畫結束后,視圖就停在結束的地方,并且此時我去訪問該視圖的屬性(也就是M的屬性),也應該就是當前看到的那個樣子。按照官方文檔的描述,我們的CAAnimation動畫都可以通過設置modelLayer到動畫結束的狀態來實現P和M的同步。
2.2 動畫的實現方式
在iOS中,實現動畫的方式主要分兩大類:
- CoreAnimation動畫
- 非CoreAnimation動畫。
CoreAnimation動畫:
CoreAnimation動畫,即基于事務的動畫,是最常見的動畫實現方式。動畫執行者是專門負責渲染的渲染進程,操作的是呈現樹 presentationLayer。我們應該盡量使用CoreAnimation來控制動畫,因為CoreAnimation是充分優化過的:
1、更高效的繪制
基于Layer的繪圖過程中,CoreAnimation通過硬件操作位圖(變換、組合等),產生動畫的速度比軟件操作的方式快很多。
基于View的繪圖過程中,view被改動時會觸發的drawRect:方法來重新繪制位圖,但是這種方式需要CPU在主線程執行,比較耗時。而CoreAnimation則盡可能的操作硬件中已緩存的位圖,來實現相同的效果,從而減少了資源損耗。
2、更高效的動畫
在動畫過程中,CoreAnimation會通過硬件來一幀一幀的繪制。你所做的就是指定動畫的起點和終點,其他的都讓CoreAnimation來做。當然你也可以自定義動畫參數,否則CoreAnimation會使用合適的默認值。
非CoreAnimation動畫:
非CoreAnimation動畫執行者是當前進程,操作的是模型樹 modelLayer。常見的有定時器動畫和手勢動畫。定時器動畫是在定時周期觸發時修改模型樹的圖層屬性;手勢動畫是手勢事件(比如UIScrollView的didScrollView)觸發時修改模型樹的圖層屬性。兩者都能達到視圖隨著時間不斷變化的效果,即實現了動畫。
非CoreAnimation動畫動畫過程中實際上不斷改動的是模型樹,而呈現樹僅僅成了模型樹的復制品,狀態與模型樹保持一致。整個過程中,主要是CPU在主線程不斷調整圖層屬性、布局計算、提交數據,沒有充分利用到CoreAnimation強大的動畫控制功能。
以上部分關于layer的描述摘自文章鏈接
3 動畫過程中的點擊交互處理
如果你想讓你做動畫的圖層響應用戶輸入:
你可以使用-hitTest:方法來判斷指定圖層是否被觸摸,這時候對呈現圖層而不是模型圖層調用-hitTest:會顯得更有意義,因為呈現圖層代表了用戶當前看到的圖層位置,而不是當前動畫結束之后的位置。
- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor whiteColor];self.testview = [[UIView alloc]initWithFrame:CGRectMake(0, 100, 50, 30)];self.testview.backgroundColor = [UIColor orangeColor];self.testview.userInteractionEnabled = NO;[self.view addSubview:self.testview];[UIView animateWithDuration:3.0 animations:^{self.testview.frame = CGRectMake(300, 100, 50, 30);}]; }- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{UITouch *touch = [touches anyObject];CGPoint point = [touch locationInView:self.view];//方案一 // if (CGRectContainsPoint(self.testview.layer.presentationLayer.frame, point)) { // [self clickPresntationLayer]; // }//方案二if ([self.testview.layer.presentationLayer hitTest:point] != nil) {[self clickPresntationLayer];} }- (void)clickPresntationLayer{self.testview.backgroundColor = [UIColor colorWithRed:(arc4random()%255/255.0) green:(arc4random()%255/255.0) blue:(arc4random()%255/255.0) alpha:1.0]; }方案一:直接使用了CGRectContainsPoint(CGrect rect, CGPoint point);方法,該方法返回一個BOOL值,判斷point是否在rect內部,剛好可以傳染presentationLayer的frame和當前點擊的point;
方案二:直接使用了hitTest:(CGPoint)p;方法,該方法返回一個CALayer對象,如果點擊的point在其內部,返回一個layer對象;
另外,對于CALayer的另一個方法:
- (BOOL)containsPoint:(CGPoint)p;
做了嘗試發現不管用,梳理一下,當前的point在self.view上,而這個方法的point是在layer內部的,所以可能不適用。
之前面試時有碰到問題,在回到UIView和CALayer的區別時:
有回答CALayer不能響應點擊事件:但現在來看,通過hitTest方法,確實是可以響應點擊事件的。
原文鏈接
總結
以上是生活随笔為你收集整理的ios动画原理 modelLayer和presentationLayer以及点击交互的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序实现直播间点赞飘心效果的示例代
- 下一篇: QQ自动登录器