9.1 CAMediaTiming協(xié)議

2018-02-24 15:07 更新

CAMediaTiming`協(xié)議

CAMediaTiming協(xié)議定義了在一段動(dòng)畫(huà)內(nèi)用來(lái)控制逝去時(shí)間的屬性的集合,CALayerCAAnimation都實(shí)現(xiàn)了這個(gè)協(xié)議,所以時(shí)間可以被任意基于一個(gè)圖層或者一段動(dòng)畫(huà)的類(lèi)控制。

持續(xù)和重復(fù)

我們?cè)诘诎苏隆帮@式動(dòng)畫(huà)”中簡(jiǎn)單提到過(guò)durationCAMediaTiming的屬性之一),duration是一個(gè)CFTimeInterval的類(lèi)型(類(lèi)似于NSTimeInterval的一種雙精度浮點(diǎn)類(lèi)型),對(duì)將要進(jìn)行的動(dòng)畫(huà)的一次迭代指定了時(shí)間。

這里的一次迭代是什么意思呢?CAMediaTiming另外還有一個(gè)屬性叫做repeatCount,代表動(dòng)畫(huà)重復(fù)的迭代次數(shù)。如果duration是2,repeatCount設(shè)為3.5(三個(gè)半迭代),那么完整的動(dòng)畫(huà)時(shí)長(zhǎng)將是7秒。

durationrepeatCount默認(rèn)都是0。但這不意味著動(dòng)畫(huà)時(shí)長(zhǎng)為0秒,或者0次,這里的0僅僅代表了“默認(rèn)”,也就是0.25秒和1次,你可以用一個(gè)簡(jiǎn)單的測(cè)試來(lái)嘗試為這兩個(gè)屬性賦多個(gè)值,如清單9.1,圖9.1展示了程序的結(jié)果。

清單9.1 測(cè)試durationrepeatCount

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UITextField *durationField;
@property (nonatomic, weak) IBOutlet UITextField *repeatField;
@property (nonatomic, weak) IBOutlet UIButton *startButton;
@property (nonatomic, strong) CALayer *shipLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the ship
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
    self.shipLayer.position = CGPointMake(150, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
}

- (void)setControlsEnabled:(BOOL)enabled
{
    for (UIControl *control in @[self.durationField, self.repeatField, self.startButton]) {
        control.enabled = enabled;
        control.alpha = enabled? 1.0f: 0.25f;
    }
}

- (IBAction)hideKeyboard
{
    ?[self.durationField resignFirstResponder];
    [self.repeatField resignFirstResponder];
}

- (IBAction)start
{
    CFTimeInterval duration = [self.durationField.text doubleValue];
    float repeatCount = [self.repeatField.text floatValue];
    //animate the ship rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = duration;
    animation.repeatCount = repeatCount;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
    //disable controls
    [self setControlsEnabled:NO];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    //reenable controls
    [self setControlsEnabled:YES];
}

@end

圖9.2 擺動(dòng)門(mén)的動(dòng)畫(huà)

對(duì)門(mén)進(jìn)行擺動(dòng)的代碼見(jiàn)清單9.2。我們用了autoreverses來(lái)使門(mén)在打開(kāi)后自動(dòng)關(guān)閉,在這里我們把repeatDuration設(shè)置為INFINITY,于是動(dòng)畫(huà)無(wú)限循環(huán)播放,設(shè)置repeatCountINFINITY也有同樣的效果。注意repeatCountrepeatDuration可能會(huì)相互沖突,所以你只要對(duì)其中一個(gè)指定非零值。對(duì)兩個(gè)屬性都設(shè)置非0值的行為沒(méi)有被定義。

清單9.2 使用autoreverses屬性實(shí)現(xiàn)門(mén)的搖擺

@interface ViewController ()

@property (nonatomic, weak) UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the door
    CALayer *doorLayer = [CALayer layer];
    doorLayer.frame = CGRectMake(0, 0, 128, 256);
    doorLayer.position = CGPointMake(150 - 64, 150);
    doorLayer.anchorPoint = CGPointMake(0, 0.5);
    doorLayer.contents = (__bridge id)[UIImage imageNamed: @"Door.png"].CGImage;
    [self.containerView.layer addSublayer:doorLayer];
    //apply perspective transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //apply swinging animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation.y";
    animation.toValue = @(-M_PI_2);
    animation.duration = 2.0;
    animation.repeatDuration = INFINITY;
    animation.autoreverses = YES;
    [doorLayer addAnimation:animation forKey:nil];
}

@end

相對(duì)時(shí)間

每次討論到Core Animation,時(shí)間都是相對(duì)的,每個(gè)動(dòng)畫(huà)都有它自己描述的時(shí)間,可以獨(dú)立地加速,延時(shí)或者偏移。

beginTime指定了動(dòng)畫(huà)開(kāi)始之前的的延遲時(shí)間。這里的延遲從動(dòng)畫(huà)添加到可見(jiàn)圖層的那一刻開(kāi)始測(cè)量,默認(rèn)是0(就是說(shuō)動(dòng)畫(huà)會(huì)立刻執(zhí)行)。

speed是一個(gè)時(shí)間的倍數(shù),默認(rèn)1.0,減少它會(huì)減慢圖層/動(dòng)畫(huà)的時(shí)間,增加它會(huì)加快速度。如果2.0的速度,那么對(duì)于一個(gè)duration為1的動(dòng)畫(huà),實(shí)際上在0.5秒的時(shí)候就已經(jīng)完成了。

timeOffsetbeginTime類(lèi)似,但是和增加beginTime導(dǎo)致的延遲動(dòng)畫(huà)不同,增加timeOffset只是讓動(dòng)畫(huà)快進(jìn)到某一點(diǎn),例如,對(duì)于一個(gè)持續(xù)1秒的動(dòng)畫(huà)來(lái)說(shuō),設(shè)置timeOffset為0.5意味著動(dòng)畫(huà)將從一半的地方開(kāi)始。

beginTime不同的是,timeOffset并不受speed的影響。所以如果你把speed設(shè)為2.0,把timeOffset設(shè)置為0.5,那么你的動(dòng)畫(huà)將從動(dòng)畫(huà)最后結(jié)束的地方開(kāi)始,因?yàn)?秒的動(dòng)畫(huà)實(shí)際上被縮短到了0.5秒。然而即使使用了timeOffset讓動(dòng)畫(huà)從結(jié)束的地方開(kāi)始,它仍然播放了一個(gè)完整的時(shí)長(zhǎng),這個(gè)動(dòng)畫(huà)僅僅是循環(huán)了一圈,然后從頭開(kāi)始播放。

可以用清單9.3的測(cè)試程序驗(yàn)證一下,設(shè)置speedtimeOffset滑塊到隨意的值,然后點(diǎn)擊播放來(lái)觀察效果(見(jiàn)圖9.3)

清單9.3 測(cè)試timeOffsetspeed屬性

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UILabel *speedLabel;
@property (nonatomic, weak) IBOutlet UILabel *timeOffsetLabel;
@property (nonatomic, weak) IBOutlet UISlider *speedSlider;
@property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;
@property (nonatomic, strong) UIBezierPath *bezierPath;
@property (nonatomic, strong) CALayer *shipLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    self.bezierPath = [[UIBezierPath alloc] init];
    [self.bezierPath moveToPoint:CGPointMake(0, 150)];
    [self.bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
    //draw the path using a CAShapeLayer
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = self.bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    //add the ship
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 64, 64);
    self.shipLayer.position = CGPointMake(0, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
    //set initial values
    [self updateSliders];
}

- (IBAction)updateSliders
{
    CFTimeInterval timeOffset = self.timeOffsetSlider.value;
    self.timeOffsetLabel.text = [NSString stringWithFormat:@"%0.2f", timeOffset];
    float speed = self.speedSlider.value;
    self.speedLabel.text = [NSString stringWithFormat:@"%0.2f", speed];
}

- (IBAction)play
{
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.timeOffset = self.timeOffsetSlider.value;
    animation.speed = self.speedSlider.value;
    animation.duration = 1.0;
    animation.path = self.bezierPath.CGPath;
    animation.rotationMode = kCAAnimationRotateAuto;
    animation.removedOnCompletion = NO;
    [self.shipLayer addAnimation:animation forKey:@"slide"];
}

@end

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)