在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

Layer 中自定義屬性的動(dòng)畫

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

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

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

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

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

間接屬性動(dòng)畫

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

如果被我們設(shè)置的屬性已經(jīng)預(yù)設(shè)好標(biāo)準(zhǔn)動(dòng)畫,那我們完全不需要編寫任何實(shí)際的動(dòng)畫代碼,因?yàn)槲覀冃薷倪@些屬性后,它們就會繼承任何被配置在當(dāng)前 CATransaction 上的動(dòng)畫設(shè)置,并且自動(dòng)執(zhí)行動(dòng)畫。

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

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

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

@interface ViewController ()

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

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

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

    // 設(shè)置默認(rèn)時(shí)間
    self.clockFace.time = [NSDate date];
}

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

@end

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

- (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 上 下載這個(gè)項(xiàng)目看看。

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

動(dòng)畫 Layer 內(nèi)容

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

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

@interface ClockFace ()

@end

@implementation ClockFace

@dynamic time;

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

@end

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

@interface ViewController () <UITextFieldDelegate>

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

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 添加時(shí)鐘面板 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 屬性的改變呢?我們需要一個(gè)無論何時(shí) time 屬性改變時(shí)都能自動(dòng)通知 CALayer 的方式,這樣它才好重繪它的內(nèi)容。我們通過覆寫 +needsDisplayForKey: 方法即可做到這一點(diǎn),如下:

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

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

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

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

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

但這還不是我們真正想要的;我們希望 time 屬性能在舊值和新值之間在幾幀之內(nèi)做一個(gè)平滑的過渡動(dòng)畫。為了實(shí)現(xiàn)這一點(diǎn),我們需要為 time 屬性指定一個(gè)動(dòng)畫(或“動(dòng)作(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)在,如果我們再次設(shè)置 time 屬性,我們就會看到 -display 被多次調(diào)用。調(diào)用的次數(shù)大約為每秒 60 次,至于動(dòng)畫的長度,默認(rèn)為 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

由于某些原因,當(dāng)我們在每個(gè)中間點(diǎn)打印 time 值時(shí),我們一直看到的是最終值。為何不能得到插值呢?因?yàn)槲覀儾榭吹氖清e(cuò)誤的 time 屬性。

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

但連接到 model Layer 的是所謂的 presentation Layer ——它是 model Layer 的一個(gè)拷貝,但它的值所表示的是 當(dāng)前的,中間動(dòng)畫狀態(tài)。如果我們修改 -display 方法去打印 Layer 的 presentationLayertime 屬性,那我們就會看到我們所期望的插值。(同時(shí)我們也使用 presentationLayertime 屬性來獲取動(dòng)畫的開始值,替代 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)在我們所要做就是畫出時(shí)鐘。我們將使用普通的 Core Graphics 函數(shù)以繪制到一個(gè) Graphics Context 上來做到這一點(diǎn),然后將產(chǎn)生出圖像設(shè)置為我們 Layer 的 contents。下面是更新后的 -display 方法:

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

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

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

    // 繪制時(shí)針
    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 設(shè)置 contents 
    self.contents = (id)UIGraphicsGetImageFromCurrentImageContext().CGImage;
    UIGraphicsEndImageContext();
}

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

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

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

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

const NSInteger hoursOnAClockFace = 12;

- (void)display
{
    // 獲取時(shí)間插值 
    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;
}

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

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

非可視屬性的動(dòng)畫

-display 里更新其它 Layer 屬性就是不必要的,因?yàn)槲覀兛梢院芎唵蔚刂苯訉θ魏芜@樣的屬性做動(dòng)畫,如同我們在第一個(gè)時(shí)鐘面板例子里所做的那樣。但如果我們設(shè)置一些其它的東西,比如某些完全和 Layer 不相關(guān)的東西,會怎樣呢?

下面的代碼使用一個(gè) CALayer 結(jié)合 AVAudioPlayer 來創(chuàng)建一個(gè)可動(dòng)畫的音量控制器。通過把音量綁定到 dynamic 的 Layer 屬性上,我們可以使用 Core Animation 的屬性插值來平滑的在兩個(gè)不同的音量之間漸變,以同樣的方式我們可以動(dòng)畫 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
{
    // 設(shè)置音量值為合適的音量插值
    self.player.volume = [self.presentationLayer volume];
}

@end

我們可以通過使用一個(gè)簡單的有著播放、停止、音量增大以及音量減小按鈕的 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 沒有可見的外觀,但它依然需要被添加到屏幕上的視圖層級里,以便動(dòng)畫能正常工作。

結(jié)論

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

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

上一篇:避免濫用單例下一篇:照片擴(kuò)展