7.3 圖層行為

2018-02-24 15:07 更新

圖層行為

現(xiàn)在來做個(gè)實(shí)驗(yàn),試著直接對(duì)UIView關(guān)聯(lián)的圖層做動(dòng)畫而不是一個(gè)單獨(dú)的圖層。清單7.4是對(duì)清單7.2代碼的一點(diǎn)修改,移除了colorLayer,并且直接設(shè)置layerView關(guān)聯(lián)圖層的背景色。

清單7.4 直接設(shè)置圖層的屬性

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //set the color of our layerView backing layer directly
    self.layerView.layer.backgroundColor = [UIColor blueColor].CGColor;
}

- (IBAction)changeColor
{
    //begin a new transaction
    [CATransaction begin];
    //set the animation duration to 1 second
    [CATransaction setAnimationDuration:1.0];
    //randomize the layer background color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.layerView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    //commit the transaction
    [CATransaction commit];
}

運(yùn)行程序,你會(huì)發(fā)現(xiàn)當(dāng)按下按鈕,圖層顏色瞬間切換到新的值,而不是之前平滑過渡的動(dòng)畫。發(fā)生了什么呢?隱式動(dòng)畫好像被UIView關(guān)聯(lián)圖層給禁用了。

試想一下,如果UIView的屬性都有動(dòng)畫特性的話,那么無論在什么時(shí)候修改它,我們都應(yīng)該能注意到的。所以,如果說UIKit建立在Core Animation(默認(rèn)對(duì)所有東西都做動(dòng)畫)之上,那么隱式動(dòng)畫是如何被UIKit禁用掉呢?

我們知道Core Animation通常對(duì)CALayer的所有屬性(可動(dòng)畫的屬性)做動(dòng)畫,但是UIView把它關(guān)聯(lián)的圖層的這個(gè)特性關(guān)閉了。為了更好說明這一點(diǎn),我們需要知道隱式動(dòng)畫是如何實(shí)現(xiàn)的。

我們把改變屬性時(shí)CALayer自動(dòng)應(yīng)用的動(dòng)畫稱作行為,當(dāng)CALayer的屬性被修改時(shí)候,它會(huì)調(diào)用-actionForKey:方法,傳遞屬性的名稱。剩下的操作都在CALayer的頭文件中有詳細(xì)的說明,實(shí)質(zhì)上是如下幾步:

  • 圖層首先檢測(cè)它是否有委托,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法。如果有,直接調(diào)用并返回結(jié)果。
  • 如果沒有委托,或者委托沒有實(shí)現(xiàn)-actionForLayer:forKey方法,圖層接著檢查包含屬性名稱對(duì)應(yīng)行為映射的actions字典。
  • 如果actions字典沒有包含對(duì)應(yīng)的屬性,那么圖層接著在它的style字典接著搜索屬性名。
  • 最后,如果在style里面也找不到對(duì)應(yīng)的行為,那么圖層將會(huì)直接調(diào)用定義了每個(gè)屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法。

所以一輪完整的搜索結(jié)束之后,-actionForKey:要么返回空(這種情況下將不會(huì)有動(dòng)畫發(fā)生),要么是CAAction協(xié)議對(duì)應(yīng)的對(duì)象,最后CALayer拿這個(gè)結(jié)果去對(duì)先前和當(dāng)前的值做動(dòng)畫。

于是這就解釋了UIKit是如何禁用隱式動(dòng)畫的:每個(gè)UIView對(duì)它關(guān)聯(lián)的圖層都扮演了一個(gè)委托,并且提供了-actionForLayer:forKey的實(shí)現(xiàn)方法。當(dāng)不在一個(gè)動(dòng)畫塊的實(shí)現(xiàn)中,UIView對(duì)所有圖層行為返回nil,但是在動(dòng)畫block范圍之內(nèi),它就返回了一個(gè)非空值。我們可以用一個(gè)demo做個(gè)簡單的實(shí)驗(yàn)(清單7.5)

清單7.5 測(cè)試UIView的actionForLayer:forKey:實(shí)現(xiàn)

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //test layer action when outside of animation block
    NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
    //begin animation block
    [UIView beginAnimations:nil context:nil];
    //test layer action when inside of animation block
    NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
    //end animation block
    [UIView commitAnimations];
}

@end

運(yùn)行程序,控制臺(tái)顯示結(jié)果如下:

$ LayerTest[21215:c07] Outside: <null>
$ LayerTest[21215:c07] Inside: <CABasicAnimation: 0x757f090>

于是我們可以預(yù)言,當(dāng)屬性在動(dòng)畫塊之外發(fā)生改變,UIView直接通過返回nil來禁用隱式動(dòng)畫。但如果在動(dòng)畫塊范圍之內(nèi),根據(jù)動(dòng)畫具體類型返回相應(yīng)的屬性,在這個(gè)例子就是CABasicAnimation(第八章“顯式動(dòng)畫”將會(huì)提到)。

當(dāng)然返回nil并不是禁用隱式動(dòng)畫唯一的辦法,CATransacition有個(gè)方法叫做+setDisableActions:,可以用來對(duì)所有屬性打開或者關(guān)閉隱式動(dòng)畫。如果在清單7.2的[CATransaction begin]之后添加下面的代碼,同樣也會(huì)阻止動(dòng)畫的發(fā)生:

[CATransaction setDisableActions:YES];

總結(jié)一下,我們知道了如下幾點(diǎn)

  • UIView關(guān)聯(lián)的圖層禁用了隱式動(dòng)畫,對(duì)這種圖層做動(dòng)畫的唯一辦法就是使用UIView的動(dòng)畫函數(shù)(而不是依賴CATransaction),或者繼承UIView,并覆蓋-actionForLayer:forKey:方法,或者直接創(chuàng)建一個(gè)顯式動(dòng)畫(具體細(xì)節(jié)見第八章)。
  • 對(duì)于單獨(dú)存在的圖層,我們可以通過實(shí)現(xiàn)圖層的-actionForLayer:forKey:委托方法,或者提供一個(gè)actions字典來控制隱式動(dòng)畫。

我們來對(duì)顏色漸變的例子使用一個(gè)不同的行為,通過給colorLayer設(shè)置一個(gè)自定義的actions字典。我們也可以使用委托來實(shí)現(xiàn),但是actions字典可以寫更少的代碼。那么到底改如何創(chuàng)建一個(gè)合適的行為對(duì)象呢?

行為通常是一個(gè)被Core Animation隱式調(diào)用的顯式動(dòng)畫對(duì)象。這里我們使用的是一個(gè)實(shí)現(xiàn)了CATransaction的實(shí)例,叫做推進(jìn)過渡。

第八章中將會(huì)詳細(xì)解釋過渡,不過對(duì)于現(xiàn)在,知道CATransition響應(yīng)CAAction協(xié)議,并且可以當(dāng)做一個(gè)圖層行為就足夠了。結(jié)果很贊,不論在什么時(shí)候改變背景顏色,新的色塊都是從左側(cè)滑入,而不是默認(rèn)的漸變效果。

清單7.6 實(shí)現(xiàn)自定義行為

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView;
@property (nonatomic, weak) IBOutlet CALayer *colorLayer;/*熱心人發(fā)現(xiàn)這里應(yīng)該改為@property (nonatomic, strong)  CALayer *colorLayer;否則運(yùn)行結(jié)果不正確。
*/

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    //create sublayer
    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add a custom action
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;
    self.colorLayer.actions = @{@"backgroundColor": transition};
    //add it to our view
    [self.layerView.layer addSublayer:self.colorLayer];
}

- (IBAction)changeColor
{
    //randomize the layer background color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}

@end

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)