ios動畫原理 modelLayer和presentationLayer以及點擊交互

我們知道,iOS的動畫根时,和其對應(yīng)的layer有關(guān)瘦赫。

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

1 動畫過程中frame的變化

為了研究動畫過程中替裆,view和Layer的frame變化校辩,做了簡單的動畫打印測試,效果如下:

image.png

由結(jié)果可知辆童,在UIView動畫的回調(diào)中添加打印宜咒,只會打印一次,且打印的是最終的view和Layer 的位置胸遇,這顯然不是想要的結(jié)果荧呐,所以,在動畫的回調(diào)block中打印是不能實現(xiàn)的;

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

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

image.png

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

侦高??問題:
1.view的frame變化為什么只有一次厌杜?動畫的過程中奉呛,view的frame不變?
2.為什么監(jiān)聽不到layer 的frame的變化夯尽? 最終通過第一次的實驗瞧壮,layer的frame是發(fā)生了變化的啊匙握?

1.2 添加CADisplayLink 打印frame的變化

帶著1.1中的兩個問題咆槽,回想到view動畫的實現(xiàn)原理,動畫的過程其實是一幀一幀顯示的圈纺,所以秦忿,每一幀對應(yīng)的view或者layer的位置是不斷變化的;

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

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


image.png

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

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

由此莽囤,證明了動畫過程

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

而動畫的view的layer朽缎,確實是和動畫相關(guān)的惨远,難道它的frame也只改變了一次谜悟?

2 關(guān)于modelLayer和presentationLayer

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


/* 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)用時北秽,返回
 *具有當前模型值的基礎(chǔ)層葡幸。當調(diào)用
 *非表示層,返回接收者贺氓。通話結(jié)果
 *產(chǎn)生展示的交易后的此方法
 *圖層已完成是不確定的蔚叨。 * /


/ *返回包含所有屬性的圖層的副本
 *在當前交易開始時,帶有任何活動的動畫
 *適用辙培。這非常接近該圖層的版本
 *當前顯示蔑水。如果圖層尚未返回nil
 *已承諾。
 *
 *嘗試以任何方式修改返回的圖層的效果是
 *未定義扬蕊。
 *
 *返回的`sublayers'搀别,`mask'和`superlayer'屬性
 *層返回這些屬性的表示形式。這個
 *進行只讀層方法厨相。例如领曼,調(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)用時蛮穿,返回
 *具有當前模型值的基礎(chǔ)層。當調(diào)用
 *非表示層毁渗,返回接收者践磅。通話結(jié)果
 *產(chǎn)生展示的交易后的此方法
 *圖層已完成是不確定的。 * /

- (instancetype)modelLayer;

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

結(jié)果1
結(jié)果2

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

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

image.png

通常么伯,我們操作的是模型樹疟暖;

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

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

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

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

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

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

image.png

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

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

  • 同步動畫滞项,
  • 動畫過程中處理用戶交互。

當模型樹上帶有動畫特征時夭坪,提交到渲染進程后文判,渲染進程會根據(jù)動畫特征,不斷修改呈現(xiàn)樹上的圖層屬性室梅,并同時不斷的在屏幕上渲染出來戏仓,這樣我們就看到了動畫。

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

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

P只負責(zé)顯示,M只負責(zé)數(shù)據(jù)的存儲和獲取间涵。

我們對layer的各種屬性賦值比如frame仁热,實際上是直接對M的屬性賦值;

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

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

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

重點: 動畫完成回到原地

可以簡單的理解為:一般情況下悴势,任意時刻P都會回到M的狀態(tài)窗宇。

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

這就是為什么動畫結(jié)束后我們看到這個視圖又回到了原來的位置泳唠,是因為我們看到在移動的是P,而指揮它移動的是A宙搬,M永遠停在原來的位置沒有動笨腥,動畫結(jié)束后A被移除,P就回到了M的懷里勇垛。

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

2.2 動畫的實現(xiàn)方式

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

  • CoreAnimation動畫
  • 非CoreAnimation動畫凑兰。

CoreAnimation動畫

CoreAnimation動畫掌桩,即基于事務(wù)的動畫,是最常見的動畫實現(xiàn)方式姑食。動畫執(zhí)行者是專門負責(zé)渲染的渲染進程波岛,操作的是呈現(xiàn)樹 presentationLayer。我們應(yīng)該盡量使用CoreAnimation來控制動畫音半,因為CoreAnimation是充分優(yōu)化過的:

1则拷、更高效的繪制

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

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

2组力、更高效的動畫

在動畫過程中省容,CoreAnimation會通過硬件來一幀一幀的繪制。你所做的就是指定動畫的起點和終點燎字,其他的都讓CoreAnimation來做腥椒。當然你也可以自定義動畫參數(shù),否則CoreAnimation會使用合適的默認值候衍。

非CoreAnimation動畫

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

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

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

3 動畫過程中的點擊交互處理

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

你可以使用-hitTest:方法來判斷指定圖層是否被觸摸钦铺,這時候?qū)Τ尸F(xiàn)圖層而不是模型圖層調(diào)用-hitTest:會顯得更有意義,因為呈現(xiàn)圖層代表了用戶當前看到的圖層位置肢预,而不是當前動畫結(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);方法,該方法返回一個BOOL值误甚,判斷point是否在rect內(nèi)部缚甩,剛好可以傳染presentationLayer的frame和當前點擊的point谱净;

image.png

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

另外郊丛,對于CALayer的另一個方法:

  • (BOOL)containsPoint:(CGPoint)p;

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

image.png

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滤馍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子底循,更是在濱河造成了極大的恐慌巢株,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熙涤,死亡現(xiàn)場離奇詭異阁苞,居然都是意外死亡,警方通過查閱死者的電腦和手機祠挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門那槽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人等舔,你說我怎么就攤上這事倦炒。” “怎么了软瞎?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拉讯。 經(jīng)常有香客問我涤浇,道長,這世上最難降的妖魔是什么魔慷? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任只锭,我火速辦了婚禮,結(jié)果婚禮上院尔,老公的妹妹穿的比我還像新娘蜻展。我一直安慰自己喉誊,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布纵顾。 她就那樣靜靜地躺著伍茄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪施逾。 梳的紋絲不亂的頭發(fā)上敷矫,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音汉额,去河邊找鬼曹仗。 笑死,一個胖子當著我的面吹牛蠕搜,可吹牛的內(nèi)容都是我干的怎茫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼妓灌,長吁一口氣:“原來是場噩夢啊……” “哼轨蛤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起旬渠,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤俱萍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后告丢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枪蘑,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年岖免,在試婚紗的時候發(fā)現(xiàn)自己被綠了岳颇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡颅湘,死狀恐怖话侧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闯参,我是刑警寧澤瞻鹏,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站鹿寨,受9級特大地震影響新博,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脚草,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一赫悄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦埂淮、人聲如沸姑隅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讲仰。三九已至,卻和暖如春误窖,著一層夾襖步出監(jiān)牢的瞬間叮盘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工霹俺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柔吼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓丙唧,卻偏偏與公主長得像愈魏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子想际,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354