日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

ios动画原理 modelLayer和presentationLayer以及点击交互

發(fā)布時(shí)間:2023/12/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ios动画原理 modelLayer和presentationLayer以及点击交互 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

ios動(dòng)畫原理 modelLayer和presentationLayer以及點(diǎn)擊交互

我們知道,iOS的動(dòng)畫,和其對(duì)應(yīng)的layer有關(guān)。

之前在開發(fā)的過程中碰到一個(gè)問題,那就是,在一個(gè)視圖的動(dòng)畫過程中,這個(gè)視圖view和Layer的frame是怎么變化的?

1 動(dòng)畫過程中frame的變化

為了研究動(dòng)畫過程中,view和Layer的frame變化,做了簡(jiǎn)單的動(dòng)畫打印測(cè)試,效果如下:

由結(jié)果可知,在UIView動(dòng)畫的回調(diào)中添加打印,只會(huì)打印一次,且打印的是最終的view和Layer 的位置,這顯然不是想要的結(jié)果,所以,在動(dòng)畫的回調(diào)block中打印是不能實(shí)現(xiàn)的;

1.1 添加KVO監(jiān)聽frame的變化

在動(dòng)畫之前,對(duì)視圖view和layer分別添加kvo監(jiān)聽其frame的變化,看是否能在動(dòng)畫過程中,視圖的frame發(fā)生變化,從而打印出對(duì)應(yīng)的frame:

如上圖打印結(jié)果,添加kvo監(jiān)聽,對(duì)應(yīng)的view和layer,只監(jiān)聽到了修改view的一次frame的變化,添加斷點(diǎn),監(jiān)聽的回調(diào)也只走一次;

??問題:
1.view的frame變化為什么只有一次?動(dòng)畫的過程中,view的frame不變?
2.為什么監(jiān)聽不到layer 的frame的變化? 最終通過第一次的實(shí)驗(yàn),layer的frame是發(fā)生了變化的啊?

1.2 添加CADisplayLink 打印frame的變化

帶著1.1中的兩個(gè)問題,回想到view動(dòng)畫的實(shí)現(xiàn)原理,動(dòng)畫的過程其實(shí)是一幀一幀顯示的,所以,每一幀對(duì)應(yīng)的view或者layer的位置是不斷變化的;

所以應(yīng)該嘗試打印在每一幀的顯示時(shí),view和layer的frame;

而這恰好可以使用系統(tǒng)自帶的CADisplayLink,它的回調(diào)頻率,和設(shè)備的刷幀是一致的,添加測(cè)試如下:

注意:添加一個(gè)dispatch_after,是為了避免一直打印,不方便查看打印結(jié)果,沒有其他作用;

打印發(fā)現(xiàn),view和layer的frame還是每次打印的最終位置的frame;

由此,證明了動(dòng)畫過程

view的frame只改變的一次,直接改到了最終的frame

而動(dòng)畫的view的layer,確實(shí)是和動(dòng)畫相關(guān)的,難道它的frame也只改變了一次?

2 關(guān)于modelLayer和presentationLayer

打開CALayer的頭文件,發(fā)現(xiàn)了其下兩個(gè)屬性:

/* 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方法的結(jié)果上調(diào)用時(shí),返回*具有當(dāng)前模型值的基礎(chǔ)層。當(dāng)調(diào)用*非表示層,返回接收者。通話結(jié)果*產(chǎn)生展示的交易后的此方法*圖層已完成是不確定的。 * // *返回包含所有屬性的圖層的副本*在當(dāng)前交易開始時(shí),帶有任何活動(dòng)的動(dòng)畫*適用。這非常接近該圖層的版本*當(dāng)前顯示。如果圖層尚未返回nil*已承諾。**嘗試以任何方式修改返回的圖層的效果是*未定義。**返回的`sublayers',`mask'和`superlayer'屬性*層返回這些屬性的表示形式。這個(gè)*進(jìn)行只讀層方法。例如,調(diào)用-hitTest:* -presentationLayer的結(jié)果將查詢演示文稿*層樹的值。 * /- (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方法的結(jié)果上調(diào)用時(shí),返回*具有當(dāng)前模型值的基礎(chǔ)層。當(dāng)調(diào)用*非表示層,返回接收者。通話結(jié)果*產(chǎn)生展示的交易后的此方法*圖層已完成是不確定的。 * /- (instancetype)modelLayer;

將回調(diào)中的layer,換成了presentationLayer,打印結(jié)果如下:

打印發(fā)現(xiàn)了一開始想要的結(jié)果,原來直接打印view的frame之變化一次是對(duì)的,真正的發(fā)生動(dòng)畫的是一個(gè)叫presentationLayer的東西;

動(dòng)畫的整個(gè)過程其實(shí)經(jīng)歷了三個(gè)樹狀結(jié)構(gòu),才顯示到了屏幕上:模型樹–>呈現(xiàn)樹–>渲染樹,如圖:

通常,我們操作的是模型樹;

在重繪周期最后,我們會(huì)將模型樹相關(guān)內(nèi)容(層次結(jié)構(gòu)、圖層屬性和動(dòng)畫)序列化,通過IPC傳遞給專門負(fù)責(zé)屏幕渲染的渲染進(jìn)程。渲染進(jìn)程拿到數(shù)據(jù)并反序列化出樹狀結(jié)構(gòu)–呈現(xiàn)樹。

這個(gè)呈現(xiàn)圖層實(shí)際上是模型圖層的復(fù)制,但是它的屬性值代表了在任何指定時(shí)刻當(dāng)前外觀效果。換句話說,你可以通過呈現(xiàn)圖層的值來獲取當(dāng)前屏幕上真正顯示出來的值

我們可以通過CALayer的presentationLayer方法來訪問對(duì)應(yīng)的呈現(xiàn)樹圖層。

注意:呈現(xiàn)圖層僅僅當(dāng)圖層首次被提交(就是首次第一次在屏幕上顯示)的時(shí)候創(chuàng)建,所以在那之前調(diào)用-presentationLayer將會(huì)返回nil。

–modelLayer方法: 在呈現(xiàn)圖層上調(diào)用–modelLayer將會(huì)返回它正在呈現(xiàn)所依賴的CALayer。通常在一個(gè)圖層上調(diào)用-modelLayer會(huì)返回–self(實(shí)際上我們已經(jīng)創(chuàng)建的原始圖層就是一種數(shù)據(jù)模型)。

一個(gè)移動(dòng)的圖層是如何通過數(shù)據(jù)模型呈現(xiàn)的:

大多數(shù)情況下,你不需要直接訪問呈現(xiàn)圖層,你可以通過和模型圖層的交互,來讓Core Animation更新顯示。

兩種情況下presentationLayer呈現(xiàn)圖層會(huì)變得很有用

  • 同步動(dòng)畫,
  • 動(dòng)畫過程中處理用戶交互。

當(dāng)模型樹上帶有動(dòng)畫特征時(shí),提交到渲染進(jìn)程后,渲染進(jìn)程會(huì)根據(jù)動(dòng)畫特征,不斷修改呈現(xiàn)樹上的圖層屬性,并同時(shí)不斷的在屏幕上渲染出來,這樣我們就看到了動(dòng)畫。

2.1 模型樹與呈現(xiàn)樹關(guān)系的比喻

在CALayer內(nèi)部,它控制著兩個(gè)屬性:presentationLayer(以下稱為P)和modelLayer(以下稱為M)。

P只負(fù)責(zé)顯示,M只負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和獲取

我們對(duì)layer的各種屬性賦值比如frame,實(shí)際上是直接對(duì)M的屬性賦值;

而P將在每一次屏幕刷新的時(shí)候回到M的狀態(tài)。

比如此時(shí)M的狀態(tài)是1,P的狀態(tài)也是1,然后我們把M的狀態(tài)改為2,那么此時(shí)P還沒有過去,也就是我們看到的狀態(tài)P還是1,在下一次屏幕刷新的時(shí)候P才變?yōu)?。而我們幾乎感知不到兩次屏幕刷新之間的間隙,所以感覺就是我們一對(duì)M賦值,P就過去了。

P就像是瞎子,M就像是瘸子,瞎子背著瘸子,瞎子每走一步(也就是每次屏幕刷新的時(shí)候)都要去問瘸子應(yīng)該怎樣走(這里的走路就是繪制內(nèi)容到屏幕上),瘸子沒法走,只能指揮瞎子背著自己走。

重點(diǎn): 動(dòng)畫完成回到原地

可以簡(jiǎn)單的理解為:一般情況下,任意時(shí)刻P都會(huì)回到M的狀態(tài)。

而當(dāng)一個(gè)CAAnimation(以下稱為A)加到了layer上面后,A就把M從P身上擠下去了。
現(xiàn)在P背著的是A,P同樣在每次屏幕刷新的時(shí)候去問他背著的那個(gè)家伙,A就指揮它從fromValue到toValue來改變值。而動(dòng)畫結(jié)束后,A會(huì)自動(dòng)被移除,這時(shí)P沒有了指揮,就只能大喊“M你在哪”,M說我還在原地沒動(dòng)呢,于是P就順聲回到M的位置了。

這就是為什么動(dòng)畫結(jié)束后我們看到這個(gè)視圖又回到了原來的位置,是因?yàn)槲覀兛吹皆谝苿?dòng)的是P,而指揮它移動(dòng)的是A,M永遠(yuǎn)停在原來的位置沒有動(dòng),動(dòng)畫結(jié)束后A被移除,P就回到了M的懷里。

動(dòng)畫結(jié)束后,P會(huì)回到M的狀態(tài)(當(dāng)然這是有前提的,因?yàn)閯?dòng)畫已經(jīng)被移除了,我們可以設(shè)置fillMode來繼續(xù)影響P),但是這通常都不是我們動(dòng)畫想要的效果。我們通常想要的是,動(dòng)畫結(jié)束后,視圖就停在結(jié)束的地方,并且此時(shí)我去訪問該視圖的屬性(也就是M的屬性),也應(yīng)該就是當(dāng)前看到的那個(gè)樣子。按照官方文檔的描述,我們的CAAnimation動(dòng)畫都可以通過設(shè)置modelLayer到動(dòng)畫結(jié)束的狀態(tài)來實(shí)現(xiàn)P和M的同步。

2.2 動(dòng)畫的實(shí)現(xiàn)方式

在iOS中,實(shí)現(xiàn)動(dòng)畫的方式主要分兩大類:

  • CoreAnimation動(dòng)畫
  • 非CoreAnimation動(dòng)畫。

CoreAnimation動(dòng)畫

CoreAnimation動(dòng)畫,即基于事務(wù)的動(dòng)畫,是最常見的動(dòng)畫實(shí)現(xiàn)方式。動(dòng)畫執(zhí)行者是專門負(fù)責(zé)渲染的渲染進(jìn)程,操作的是呈現(xiàn)樹 presentationLayer。我們應(yīng)該盡量使用CoreAnimation來控制動(dòng)畫,因?yàn)镃oreAnimation是充分優(yōu)化過的:

1、更高效的繪制

基于Layer的繪圖過程中,CoreAnimation通過硬件操作位圖(變換、組合等),產(chǎn)生動(dòng)畫的速度比軟件操作的方式快很多。

基于View的繪圖過程中,view被改動(dòng)時(shí)會(huì)觸發(fā)的drawRect:方法來重新繪制位圖,但是這種方式需要CPU在主線程執(zhí)行,比較耗時(shí)。而CoreAnimation則盡可能的操作硬件中已緩存的位圖,來實(shí)現(xiàn)相同的效果,從而減少了資源損耗。

2、更高效的動(dòng)畫

在動(dòng)畫過程中,CoreAnimation會(huì)通過硬件來一幀一幀的繪制。你所做的就是指定動(dòng)畫的起點(diǎn)和終點(diǎn),其他的都讓CoreAnimation來做。當(dāng)然你也可以自定義動(dòng)畫參數(shù),否則CoreAnimation會(huì)使用合適的默認(rèn)值。

非CoreAnimation動(dòng)畫

非CoreAnimation動(dòng)畫執(zhí)行者是當(dāng)前進(jìn)程,操作的是模型樹 modelLayer。常見的有定時(shí)器動(dòng)畫和手勢(shì)動(dòng)畫。定時(shí)器動(dòng)畫是在定時(shí)周期觸發(fā)時(shí)修改模型樹的圖層屬性;手勢(shì)動(dòng)畫是手勢(shì)事件(比如UIScrollView的didScrollView)觸發(fā)時(shí)修改模型樹的圖層屬性。兩者都能達(dá)到視圖隨著時(shí)間不斷變化的效果,即實(shí)現(xiàn)了動(dòng)畫。

非CoreAnimation動(dòng)畫動(dòng)畫過程中實(shí)際上不斷改動(dòng)的是模型樹,而呈現(xiàn)樹僅僅成了模型樹的復(fù)制品,狀態(tài)與模型樹保持一致。整個(gè)過程中,主要是CPU在主線程不斷調(diào)整圖層屬性、布局計(jì)算、提交數(shù)據(jù),沒有充分利用到CoreAnimation強(qiáng)大的動(dòng)畫控制功能。

以上部分關(guān)于layer的描述摘自文章鏈接

3 動(dòng)畫過程中的點(diǎn)擊交互處理

如果你想讓你做動(dòng)畫的圖層響應(yīng)用戶輸入

你可以使用-hitTest:方法來判斷指定圖層是否被觸摸,這時(shí)候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會(huì)顯得更有意義,因?yàn)槌尸F(xiàn)圖層代表了用戶當(dāng)前看到的圖層位置,而不是當(dāng)前動(dòng)畫結(jié)束之后的位置。

- (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);方法,該方法返回一個(gè)BOOL值,判斷point是否在rect內(nèi)部,剛好可以傳染presentationLayer的frame和當(dāng)前點(diǎn)擊的point;

方案二:直接使用了hitTest:(CGPoint)p;方法,該方法返回一個(gè)CALayer對(duì)象,如果點(diǎn)擊的point在其內(nèi)部,返回一個(gè)layer對(duì)象;

另外,對(duì)于CALayer的另一個(gè)方法:

  • (BOOL)containsPoint:(CGPoint)p;

做了嘗試發(fā)現(xiàn)不管用,梳理一下,當(dāng)前的point在self.view上,而這個(gè)方法的point是在layer內(nèi)部的,所以可能不適用。

之前面試時(shí)有碰到問題,在回到UIView和CALayer的區(qū)別時(shí):

有回答CALayer不能響應(yīng)點(diǎn)擊事件:但現(xiàn)在來看,通過hitTest方法,確實(shí)是可以響應(yīng)點(diǎn)擊事件的。

原文鏈接

總結(jié)

以上是生活随笔為你收集整理的ios动画原理 modelLayer和presentationLayer以及点击交互的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。