鍍金池/ 教程/ iOS/ Layer 中自定義屬性的動畫
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應用數(shù)據(jù)腳本化
一個完整的 Core Data 應用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡應用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應式 Android 應用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應式視圖
Square Register 中的擴張
DTrace
基礎集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設計的藝術
導航應用
線程安全類的設計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術:Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

Layer 中自定義屬性的動畫

默認情況下,CALayer 及其子類的絕大部分標準屬性都可以執(zhí)行動畫,無論是添加一個 CAAnimation 到 Layer(顯式動畫),亦或是為屬性指定一個動作然后修改它(隱式動畫)。

但有時候我們希望能同時為好幾個屬性添加動畫,使它們看起來像是一個動畫一樣;或者,我們需要執(zhí)行的動畫不能通過使用標準 Layer 屬性動畫來實現(xiàn)。

在本文中,我們將討論如何子類化 CALayer 并添加我們自己的屬性,以便比較容易地創(chuàng)建那些如果以其他方式實現(xiàn)起來會很麻煩的動畫效果。

一般說來,我們希望添加到 CALayer 的子類上的可動畫屬性有三種類型:

  • 能間接動畫 Layer (或其子類)的一個或多個標準屬性的屬性。
  • 能觸發(fā) Layer 背后的圖像(即 contents 屬性)重繪的屬性。
  • 不涉及 Layer 重繪或?qū)θ魏我延袑傩詧?zhí)行動畫的屬性。

間接屬性動畫

能間接修改其它標準 Layer 屬性的自定義屬性是這些選項中最簡單的。它們僅僅只是自定義 setter 方法。然后將它們的輸入轉(zhuǎn)換為適用于創(chuàng)建動畫的一個或多個不同的值。

如果被我們設置的屬性已經(jīng)預設好標準動畫,那我們完全不需要編寫任何實際的動畫代碼,因為我們修改這些屬性后,它們就會繼承任何被配置在當前 CATransaction 上的動畫設置,并且自動執(zhí)行動畫。

換句話說,即使 CALayer 不知道如何對我們自定義的屬性進行動畫,它依然能對因自定義屬性被改變而引起的其它可見副作用進行動畫,而這恰好就是我們所需要的。

為了演示這種方法,讓我們來創(chuàng)建一個簡單的模擬時鐘,之后我們可以使用被聲明為 NSDate 類型 time 屬性來設置它的時間。我會將從創(chuàng)建一個靜態(tài)的時鐘面盤開始。這個時鐘包含三個 CAShapeLayer 實例 —— 一個用于時鐘面盤的圓形 Layer 和兩個用于時針和分針的長方形 Sublayer。

@interface ClockFace: CAShapeLayer

@property (nonatomic, strong) NSDate *time;

@end

@interface ClockFace ()

// 私有屬性
@property (nonatomic, strong) CAShapeLayer *hourHand;
@property (nonatomic, strong) CAShapeLayer *minuteHand;

@end

@implementation ClockFace

- (id)init
{
    if ((self = [super init]))
    {
        self.bounds = CGRectMake(0, 0, 200, 200);
        self.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
        self.fillColor = [UIColor whiteColor].CGColor;
        self.strokeColor = [UIColor blackColor].CGColor;
        self.lineWidth = 4;

        self.hourHand = [CAShapeLayer layer];
        self.hourHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-2, -70, 4, 70)].CGPath;
        self.hourHand.fillColor = [UIColor blackColor].CGColor;
        self.hourHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
        [self addSublayer:self.hourHand];

        self.minuteHand = [CAShapeLayer layer];
        self.minuteHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-1, -90, 2, 90)].CGPath;
        self.minuteHand.fillColor = [UIColor blackColor].CGColor;
        self.minuteHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
        [self addSublayer:self.minuteHand];
    }
    return self;
}

@end

同時我們要設置一個包含 UIDatePicker 的基本的 View Controller,這樣我們就能測試我們的 Layer (日期選擇器在 Storyboard 里設置)了:

@interface ViewController ()

@property (nonatomic, strong) IBOutlet UIDatePicker *datePicker;
@property (nonatomic, strong) ClockFace *clockFace;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 添加時鐘面板 Layer
    self.clockFace = [[ClockFace alloc] init];
    self.clockFace.position = CGPointMake(self.view.bounds.size.width / 2, 150);
    [self.view.layer addSublayer:self.clockFace];

    // 設置默認時間
    self.clockFace.time = [NSDate date];
}

- (IBAction)setTime
{
    self.clockFace.time = self.datePicker.date;
}

@end

現(xiàn)在我們只需要實現(xiàn) time 屬性的 setter 方法。這個方法使用 NSCalendar 將時間變?yōu)樾r和分鐘,之后我們將它們轉(zhuǎn)換為角坐標。然后我們就可以使用這些角度去生成兩個 CGAffineTransform 以旋轉(zhuǎn)時針和分針。

- (void)setTime:(NSDate *)time
{
    _time = time;

    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *components = [calendar components:NSHourCalendarUnit | NSMinuteCalendarUnit fromDate:time];
    self.hourHand.affineTransform = CGAffineTransformMakeRotation(components.hour / 12.0 * 2.0 * M_PI);
    self.minuteHand.affineTransform = CGAffineTransformMakeRotation(components.minute / 60.0 * 2.0 * M_PI);
}

結(jié)果看起來像這樣:

http://wiki.jikexueyuan.com/project/objc/images/12-18.gif" alt="" />

你可以 從 GitHub 上 下載這個項目看看。

如你所見,我們實在沒有做什么太費腦筋的事情;我們并沒有創(chuàng)建一個新的可動畫屬性,而只是在單個方法里設置了幾個標準可動畫 Layer 屬性而已。然而,如果我們想創(chuàng)建的動畫并不能映射到任何已有的 Layer 屬性上時,該怎么辦呢?

動畫 Layer 內(nèi)容

假設不使用幾個分離的 Layer 來實現(xiàn)我們的時鐘面板,那我們可以改用 Core Graphics 來繪制時鐘。(這通常會降低性能,但我們可以假想我們所要實現(xiàn)的效果需要許多復雜的繪圖操作,而它們很難用常規(guī)的 Layer 屬性和 transform 來復制。)我們要怎么做呢?

NSManagedObject 很類似, CALayer 具有為任何被聲明的屬性生成 dynamic 的 setter 和 getter 的能力。在我們當前的實現(xiàn)中,我們讓編譯器去 synthesize 了 time 屬性的 ivar 和 getter 方法,而我們自己實現(xiàn)了 setter 方法。但讓我們來改變一下:丟棄我們的 setter 并將屬性標記為 @dynamic 。同時我們也丟棄分離的時針和分針 Layer ,因為我們將自己去繪制它們。

@interface ClockFace ()

@end

@implementation ClockFace

@dynamic time;

- (id)init
{
    if ((self = [super init]))
    {
        self.bounds = CGRectMake(0, 0, 200, 200);
    }
    return self;
}

@end

在我們開始之前,需要先做一個小調(diào)整:因為不幸的是,CALayer 不知道如何對 NSDate 屬性進行插值(interpolate)(例如,雖然它可以處理數(shù)字類型和其它例如 CGColorCGAffineTransform 這樣的類型,但它不能自動生成不同的 NSDate 實例之間的中間值)。我們可以保留我們的自定義 setter 方法并用它設置另一個等價于 NSTimeInterval 的動態(tài)屬性(這是一個數(shù)字值,可以被插值),但為了保持例子的簡單性,我們會用一個浮點值替換 NSDate 屬性來表征時鐘的小時。我們還更新了用戶界面,現(xiàn)在使用一個簡單的 UITextField 來設置浮點值,而不再使用日期選擇器:

@interface ViewController () <UITextFieldDelegate>

@property (nonatomic, strong) IBOutlet UITextField *textField;
@property (nonatomic, strong) ClockFace *clockFace;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 添加時鐘面板 Layer
    self.clockFace = [[ClockFace alloc] init];
    self.clockFace.position = CGPointMake(self.view.bounds.size.width / 2, 150);
    [self.view.layer addSublayer:self.clockFace];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    self.clockFace.time = [textField.text floatValue];
}

@end

現(xiàn)在,既然我們已經(jīng)移除了自定義的 setter 方法,那我們要如何才能知曉 time 屬性的改變呢?我們需要一個無論何時 time 屬性改變時都能自動通知 CALayer 的方式,這樣它才好重繪它的內(nèi)容。我們通過覆寫 +needsDisplayForKey: 方法即可做到這一點,如下:

+ (BOOL)needsDisplayForKey:(NSString *)key
{
    if ([@"time" isEqualToString:key])
    {
        return YES;
    }
    return [super needsDisplayForKey:key];
}

這就告訴了 Layer ,無論何時 time 屬性被修改,它都需要調(diào)用 -display 方法?,F(xiàn)在我們就覆寫 -display 方法,添加一個 NSLog 語句打印出 time 的值:

- (void)display
{
    NSLog(@"time: %f", self.time);
}

如果我們設置 time 屬性為 1.5 ,我們就會看到 -display 被調(diào)用,打印出新值:

2014-04-28 22:37:04.253 ClockFace[49145:60b] time: 1.500000

但這還不是我們真正想要的;我們希望 time 屬性能在舊值和新值之間在幾幀之內(nèi)做一個平滑的過渡動畫。為了實現(xiàn)這一點,我們需要為 time 屬性指定一個動畫(或“動作(action)”),而通過覆寫 -actionForKey: 方法就能做到:

- (id<CAAction>)actionForKey:(NSString *)key
{
    if ([key isEqualToString:@"time"])
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.fromValue = @(self.time);
        return animation;
    }
    return [super actionForKey:key];
}

現(xiàn)在,如果我們再次設置 time 屬性,我們就會看到 -display 被多次調(diào)用。調(diào)用的次數(shù)大約為每秒 60 次,至于動畫的長度,默認為 0.25 秒,大約是 15 幀:

2014-04-28 22:37:04.253 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.255 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.351 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.370 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.388 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.407 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.425 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.443 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.461 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.479 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.497 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.515 ClockFace[49145:60b] time: 1.500000
2014-04-28 22:37:04.755 ClockFace[49145:60b] time: 1.500000

由于某些原因,當我們在每個中間點打印 time 值時,我們一直看到的是最終值。為何不能得到插值呢?因為我們查看的是錯誤的 time 屬性。

當你設置某個 CALayer 的某個屬性,你實際設置的是 model Layer 的值 —— 這里的 model Layer 表示正在進行的動畫結(jié)束時, Layer 所達到的最終狀態(tài)。如果你取 model Layer 的值,它就總是給你它被設置到的最終值。

但連接到 model Layer 的是所謂的 presentation Layer ——它是 model Layer 的一個拷貝,但它的值所表示的是 當前的,中間動畫狀態(tài)。如果我們修改 -display 方法去打印 Layer 的 presentationLayertime 屬性,那我們就會看到我們所期望的插值。(同時我們也使用 presentationLayertime 屬性來獲取動畫的開始值,替代 self.time ):

- (id<CAAction>)actionForKey:(NSString *)key
{
    if ([key isEqualToString:@"time"])
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.fromValue = @([[self presentationLayer] time]);
        return animation;
    }
    return [super actionForKey:key];
}

- (void)display
{
    NSLog(@"time: %f", [[self presentationLayer] time]);
}

下面是打印出的值:

2014-04-28 22:43:31.200 ClockFace[49176:60b] time: 0.000000
2014-04-28 22:43:31.203 ClockFace[49176:60b] time: 0.002894
2014-04-28 22:43:31.263 ClockFace[49176:60b] time: 0.363371
2014-04-28 22:43:31.300 ClockFace[49176:60b] time: 0.586421
2014-04-28 22:43:31.318 ClockFace[49176:60b] time: 0.695179
2014-04-28 22:43:31.336 ClockFace[49176:60b] time: 0.803713
2014-04-28 22:43:31.354 ClockFace[49176:60b] time: 0.912598
2014-04-28 22:43:31.372 ClockFace[49176:60b] time: 1.021573
2014-04-28 22:43:31.391 ClockFace[49176:60b] time: 1.134173
2014-04-28 22:43:31.409 ClockFace[49176:60b] time: 1.242892
2014-04-28 22:43:31.427 ClockFace[49176:60b] time: 1.352016
2014-04-28 22:43:31.446 ClockFace[49176:60b] time: 1.460729
2014-04-28 22:43:31.464 ClockFace[49176:60b] time: 1.500000
2014-04-28 22:43:31.636 ClockFace[49176:60b] time: 1.500000

所以現(xiàn)在我們所要做就是畫出時鐘。我們將使用普通的 Core Graphics 函數(shù)以繪制到一個 Graphics Context 上來做到這一點,然后將產(chǎn)生出圖像設置為我們 Layer 的 contents。下面是更新后的 -display 方法:

- (void)display
{
    // 獲取時間插值
    float time = [self.presentationLayer time];

    // 創(chuàng)建繪制上下文
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 繪制時鐘面板
    CGContextSetLineWidth(ctx, 4);
    CGContextStrokeEllipseInRect(ctx, CGRectInset(self.bounds, 2, 2));

    // 繪制時針
    CGFloat angle = time / 12.0 * 2.0 * M_PI;
    CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
    CGContextSetLineWidth(ctx, 4);
    CGContextMoveToPoint(ctx, center.x, center.y);
    CGContextAddLineToPoint(ctx, center.x + sin(angle) * 80, center.y - cos(angle) * 80);
    CGContextStrokePath(ctx);

    // 繪制分針
    angle = (time - floor(time)) * 2.0 * M_PI;
    CGContextSetLineWidth(ctx, 2);
    CGContextMoveToPoint(ctx, center.x, center.y);
    CGContextAddLineToPoint(ctx, center.x + sin(angle) * 90, center.y - cos(angle) * 90);
    CGContextStrokePath(ctx);

    //set backing image 設置 contents 
    self.contents = (id)UIGraphicsGetImageFromCurrentImageContext().CGImage;
    UIGraphicsEndImageContext();
}

結(jié)果看起來如下:

http://wiki.jikexueyuan.com/project/objc/images/12-19.gif" alt="" />

如你所見,不同于第一個時鐘動畫,隨著時針的變化,分針實際上對每一個小時都會轉(zhuǎn)上滿滿一圈(就像一個真正的時鐘那樣),而不僅僅只是通過最短的路徑移動到它的最終位置;因為我們正在動畫的是 time 值本身而不僅僅是時針或分針的位置,所以上下文信息被保留了。

通過這樣的方式繪制一個時鐘并不是很理想,因為 Core Graphics 函數(shù)沒有硬件加速,可能會引起動畫幀數(shù)的下降。另一種能每秒重繪 contents 圖像 60 次的方式是用一個數(shù)組存儲一些預先繪制好的圖像,然后基于合適的插值簡單的選擇對應的圖像即可。實現(xiàn)代碼大概如下:

const NSInteger hoursOnAClockFace = 12;

- (void)display
{
    // 獲取時間插值 
    float time = [self.presentationLayer time] / hoursOnAClockFace;

    // 從之前定義好的圖像數(shù)組里獲取圖像幀
    NSInteger numberOfFrames = [self.frames count];
    NSInteger index = round(time * numberOfFrames) % numberOfFrames;
    UIImage *frame = self.frames[index];
    self.contents = (id)frame.CGImage;
}

通過避免在每一幀里都用昂貴的軟件繪制,我們能改善動畫的性能,但代價是我們需要在內(nèi)存里存儲所有預先繪制的動畫幀圖像,對于一個復雜的動畫來說,這可能造成驚人的內(nèi)存浪費。

但這提出了一個有趣的可能性。如果我們完全不在 -display 里更新 contents 圖像會發(fā)生什么?我們做一些其它的事情怎樣?

非可視屬性的動畫

-display 里更新其它 Layer 屬性就是不必要的,因為我們可以很簡單地直接對任何這樣的屬性做動畫,如同我們在第一個時鐘面板例子里所做的那樣。但如果我們設置一些其它的東西,比如某些完全和 Layer 不相關的東西,會怎樣呢?

下面的代碼使用一個 CALayer 結(jié)合 AVAudioPlayer 來創(chuàng)建一個可動畫的音量控制器。通過把音量綁定到 dynamic 的 Layer 屬性上,我們可以使用 Core Animation 的屬性插值來平滑的在兩個不同的音量之間漸變,以同樣的方式我們可以動畫 Layer 上的任何自定義屬性:

@interface AudioLayer : CALayer

- (id)initWithAudioFileURL:(NSURL *)URL;

@property (nonatomic, assign) float volume;

- (void)play;
- (void)stop;
- (BOOL)isPlaying;

@end

@interface AudioLayer ()

@property (nonatomic, strong) AVAudioPlayer *player;

@end

@implementation AudioLayer

@dynamic volume;

- (id)initWithAudioFileURL:(NSURL *)URL
{
    if ((self = [self init]))
    {
        self.volume = 1.0;
        self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:NULL];
    }
    return self;
}

- (void)play
{
    [self.player play];
}

- (void)stop
{
    [self.player stop];
}

- (BOOL)isPlaying
{
    return self.player.playing;
}

+ (BOOL)needsDisplayForKey:(NSString *)key
{
    if ([@"volume" isEqualToString:key])
    {
        return YES;
    }
    return [super needsDisplayForKey:key];
}

- (id<CAAction>)actionForKey:(NSString *)key
{
    if ([key isEqualToString:@"volume"])
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.fromValue = @([[self presentationLayer] volume]);
        return animation;
    }
    return [super actionForKey:key];
}

- (void)display
{
    // 設置音量值為合適的音量插值
    self.player.volume = [self.presentationLayer volume];
}

@end

我們可以通過使用一個簡單的有著播放、停止、音量增大以及音量減小按鈕的 View Controller 來做測試:

@interface ViewController ()

@property (nonatomic, strong) AudioLayer *audioLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURL *musicURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"music" ofType:@"caf"]];
    self.audioLayer = [[AudioLayer alloc] initWithAudioFileURL:musicURL];
    [self.view.layer addSublayer:self.audioLayer];
}

- (IBAction)playPauseMusic:(UIButton *)sender
{
    if ([self.audioLayer isPlaying])
    {
        [self.audioLayer stop];
        [sender setTitle:@"Play Music" forState:UIControlStateNormal];
    }
    else
    {
        [self.audioLayer play];
        [sender setTitle:@"Pause Music" forState:UIControlStateNormal];
    }
}

- (IBAction)fadeIn
{
    self.audioLayer.volume = 1;
}

- (IBAction)fadeOut
{
    self.audioLayer.volume = 0;
}

@end

注意:盡管我們的 Layer 沒有可見的外觀,但它依然需要被添加到屏幕上的視圖層級里,以便動畫能正常工作。

結(jié)論

CALayer 的 dynamic 屬性提供了一中簡單的機制來實現(xiàn)任何形式的動畫 —— 不僅僅只是內(nèi)建的那些。而通過覆寫 -display 方法,我們可以使用這些屬性去控制任何我們想控制的東西,甚至是音量值這樣的東西。

通過使用這些屬性,我們不僅僅避免了重復造輪子,同時還確保了我們的自定義動畫能與標準動畫的時機和控制函數(shù)協(xié)同工作,以此就能非常容易地與其它動畫屬性同步。

上一篇:避免濫用單例下一篇:照片擴展