呈現與模型(隱式動畫),呈現模型動畫
呈現與模型
CALayer
的屬性行為其實很不正常,因為改變一個圖層的屬性並沒有立刻生效,而是通過一段時間漸層更新。這是怎麼做到的呢?
當你改變一個圖層的屬性,屬性值的確是立刻更新的(如果你讀取它的資料,你會發現它的值在你設定它的那一刻就已經生效了),但是螢幕上並沒有馬上發生改變。這是因為你設定的屬性並沒有直接調整圖層的外觀,相反,他只是定義了圖層動畫結束之後將要變化的外觀。
當設定CALayer
的屬性,實際上是在定義當前事務結束之後圖層如何顯示的模型。Core Animation扮演了一個控制器的角色,並且負責根據圖層行為和事務設定去不斷更新視圖的這些屬性在螢幕上的狀態。
我們討論的就是一個典型的微型MVC模式。CALayer
是一個串連使用者介面(就是MVC中的view)虛構的類,但是在介面本身這個情境下,CALayer
的行為更像是儲存了視圖如何顯示和動畫的資料模型。實際上,在蘋果自己的文檔中,圖層樹通常都是值的圖層樹模型。
在iOS中,螢幕每秒鐘重繪60次。如果動畫時間長度比60分之一秒要長,Core Animation就需要在設定一次新值和新值生效之間,對螢幕上的圖層進行重新組織。這意味著CALayer
除了“真實”值(就是你設定的值)之外,必須要知道當前顯示在螢幕上的屬性值的記錄。
每個圖層屬性的顯示值都被儲存在一個叫做呈現圖層的獨立圖層當中,他可以通過-presentationLayer
方法來訪問。這個呈現圖層實際上是模型圖層的複製,但是它的屬性值代表了在任何指定時刻當前外觀效果。換句話說,你可以通過呈現圖層的值來擷取當前螢幕上真正顯示出來的值(圖7.4)。
我們在第一章中提到除了圖層樹,另外還有呈現樹。呈現樹通過圖層樹中所有圖層的呈現圖層所形成。注意呈現圖層僅僅當圖層首次被提交(就是首次第一次在螢幕上顯示)的時候建立,所以在那之前調用-presentationLayer
將會返回nil
。
你可能注意到有一個叫做–modelLayer
的方法。在呈現圖層上調用–modelLayer
將會返回它正在呈現所依賴的CALayer
。通常在一個圖層上調用-modelLayer
會返回–self
(實際上我們已經建立的原始圖層就是一種資料模型)。
圖7.4 一個移動的圖層是如何通過資料模型呈現的
大多數情況下,你不需要直接存取呈現圖層,你可以通過和模型圖層的互動,來讓Core Animation更新顯示。兩種情況下呈現圖層會變得很有用,一個是同步動畫,一個是處理使用者互動。
- 如果你在實現一個基於定時器的動畫(見第11章“基於定時器的動畫”),而不僅僅是基於事務的動畫,這個時候準確地知道在某一時刻圖層顯示在什麼位置就會對正確擺放圖層很有用了。
- 如果你想讓你做動畫的圖層響應使用者輸入,你可以使用
-hitTest:
方法(見第三章“圖層幾何學”)來判斷指定圖層是否被觸摸,這時候對呈現圖層而不是模型圖層調用-hitTest:
會顯得更有意義,因為呈現圖層代表了使用者當前看到的圖層位置,而不是當前動畫結束之後的位置。
我們可以用一個簡單的案例來證明後者(見清單7.7)。在這個例子中,點擊螢幕上的任意位置將會讓圖層平移到那裡。點擊圖層本身可以隨機改變它的顏色。我們通過對呈現圖層調用-hitTest:
來判斷是否被點擊。
如果修改代碼讓-hitTest:
直接作用於colorLayer而不是呈現圖層,你會發現當圖層移動的時候它並不能正確顯示。這時候你就需要點擊圖層將要移動到的位置而不是圖層本身來響應點擊(這就是為什麼用呈現圖層來響應互動的原因)。
清單7.7 使用presentationLayer
圖層來判斷當前圖層位置
1 @interface ViewController () 2 3 @property (nonatomic, strong) CALayer *colorLayer; 4 5 @end 6 7 @implementation ViewController 8 9 - (void)viewDidLoad10 {11 [super viewDidLoad];12 //create a red layer13 self.colorLayer = [CALayer layer];14 self.colorLayer.frame = CGRectMake(0, 0, 100, 100);15 self.colorLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);16 self.colorLayer.backgroundColor = [UIColor redColor].CGColor;17 [self.view.layer addSublayer:self.colorLayer];18 }19 20 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event21 {22 //get the touch point23 CGPoint point = [[touches anyObject] locationInView:self.view];24 //check if we've tapped the moving layer25 if ([self.colorLayer.presentationLayer hitTest:point]) {26 //randomize the layer background color27 CGFloat red = arc4random() / (CGFloat)INT_MAX;28 CGFloat green = arc4random() / (CGFloat)INT_MAX;29 CGFloat blue = arc4random() / (CGFloat)INT_MAX;30 self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;31 } else {32 //otherwise (slowly) move the layer to new position33 [CATransaction begin];34 [CATransaction setAnimationDuration:4.0];35 self.colorLayer.position = point;36 [CATransaction commit];37 }38 }39 @end40 41
View Code