iOS 动画 第八章 显示动画

      //CABasicAnimation
    //[self basicAnimationTest];
    
    //animationDelegate
    //[self animationDelegateTest];
    
    //clockViewAnimationTest
    //[self clockViewAnimationTest];
    
    //keyframeAnimation
    //[self keyframeAnimationTest];
    
    //CGPath
    //[self keyframeAnimationPathTest];
    
    //虚拟属性
    //[self valueRotationTest];
    //[self virtualValueRotationTest];

    //动画数组
    //[self animationGroupTest];
    
    //过渡
    //[self transitionTest];
    //自定义动画
    //[self transitionSelfTest];
    
    //截图
    //[self performTransition];
    
    //取消动画
    //[self cancelAnimationTest];
    
    //duration repeatCount
    //[self durationAndRepeatCountTest];
    
    //repeatDuration
    //[self repeatDurationTest];
    
    //timeOffset and speed
    //[self timeOffsetAndSpeedTest];
    
    //handle animation
    //[self handleAnimationTest];

属性动画

//属性动画:作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。 分为两种:基础和关键帧

基础动画

//CABasicAnimation是CAPropertyAnimation的一个子类,而CAPropertyAnimation的父类是CAAnimation,CAAnimation同时也是Core Animation所有动画类型的抽象基类。
//CAAnimation提供了一个计时函数,一个委托(用于反馈动画状态)以及一个removedOnCompletion,用于标识动画是否该在结束后自动释放(默认YES,为了防止内存泄露)。CAAnimation同时实现了一些协议,包括CAAction(允许CAAnimation的子类可以提供图层行为),以及CAMediaTiming
//CAPropertyAnimation : keyPath 仅可以作用于图层本身的属性,而且还包含了它的子成员的属性,甚至是一些虚拟的属性
//CABasicAnimation继承于CAPropertyAnimation,add property id fromValue id toValue id byValue
//fromValue代表了动画开始之前属性的值,toValue代表了动画结束之后的值,byValue代表了动画执行过程中改变的值。 只需要指定toValue或者byValue
//Type Object Type Code Example
//CGFloat NSNumber id obj = @(float);
//CGPoint NSValue id obj = [NSValue valueWithCGPoint:point);
//CGSize NSValue id obj = [NSValue valueWithCGSize:size);
//CGRect NSValue id obj = [NSValue valueWithCGRect:rect);
//CATransform3D NSValue id obj = [NSValue valueWithCATransform3D:transform);
//CGImageRef id id obj = (__bridge id)imageRef;
//CGColorRef id id obj = (__bridge id)colorRef;

- (void)basicAnimationTest {
    layerView  = [[UIView alloc] init];
    layerView.frame = CGRectMake(0, 20, 320, 320);
    [self.view addSubview:layerView];
    //add change color button
    UIButton *changeColorBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    changeColorBtn.frame = CGRectMake(50, 200, 200, 44.0);
    [changeColorBtn setTitle:@"change Color" forState:(UIControlStateNormal)];
    [changeColorBtn addTarget:self action:@selector(changeColorBasicAnimationAction2) forControlEvents:UIControlEventTouchUpInside];
    changeColorBtn.backgroundColor = [UIColor redColor];
    [layerView addSubview:changeColorBtn];
    
    //create sublayer
    colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0f);
    colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add it to our view
    [layerView.layer addSublayer:colorLayer];
}

- (void)changeColorBasicAnimationAction {
    //create a new random color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    
    //create a basic animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"backgroundColor";
    //因为动画并没有改变图层的模型,而只是呈现, 一旦动画结束并从图层上移除之后,图层就立刻恢复到之前定义的外观状态 从没改变过backgroundColor属性,所以图层就返回到原始的颜色
    //在使用隐式动画的时候,实际上它就是用例子中CABasicAnimation来实现的(回忆第七章,我们在-actionForLayer:forKey:委托方法打印出来的结果就是CABasicAnimation)。
    //animation.fromValue = (__bridge id)colorLayer.backgroundColor;
    //colorLayer.backgroundColor = color.CGColor;
    
    //由于这里的图层并不是UIView关联的图层,我们需要用CATransaction来禁用隐式动画行为,否则默认的图层行为会干扰我们的显式动画
    CALayer *layer = colorLayer.presentationLayer ?: colorLayer;
    animation.fromValue = (__bridge id)layer.backgroundColor;
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    colorLayer.backgroundColor = color.CGColor;
    [CATransaction commit];
    
    animation.toValue = (__bridge id)color.CGColor;
    //apply animation to layer
    [colorLayer addAnimation:animation forKey:nil];
}
//避免在每次动画时候都重复CATransaction的代码。
- (void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer {
    //set the from value (using presentation layer if available)
    animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath];
    //update the property in advance
    //note: this approach will only work if toValue != nil
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [layer setValue:animation.toValue forKey:animation.keyPath];
    [CATransaction commit];
    //apply animation to layer
    [layer addAnimation:animation forKey:nil];
}

- (void)changeColorBasicAnimationAction2 {
    //create a new random color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    
    //create a basic animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.toValue = (__bridge id)color.CGColor;
    //apply animation without snap-back
    [self applyBasicAnimation:animation toLayer:colorLayer];
}

//CAAnimationDelegate
- (void)animationDelegateTest {
    layerView  = [[UIView alloc] init];
    layerView.frame = CGRectMake(0, 20, 320, 320);
    [self.view addSubview:layerView];
    //add change color button
    UIButton *changeColorBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    changeColorBtn.frame = CGRectMake(50, 200, 200, 44.0);
    [changeColorBtn setTitle:@"change Color" forState:(UIControlStateNormal)];
    [changeColorBtn addTarget:self action:@selector(changeColorAnimationDelegateAction) forControlEvents:UIControlEventTouchUpInside];
    changeColorBtn.backgroundColor = [UIColor redColor];
    [layerView addSubview:changeColorBtn];
    
    //create sublayer
    colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0f);
    colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add it to our view
    [layerView.layer addSublayer:colorLayer];

}

- (void)changeColorAnimationDelegateAction {
    //create a new random color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    //create a basic animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.toValue = (__bridge id)color.CGColor;
    animation.delegate = self;
    //apply animation to layer
    [colorLayer addAnimation:animation forKey:nil];
}
/*
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {
    //set backgroundColor property to match animation toValue
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
    [CATransaction commit];
}
 */

//clockViewAnimation
- (void)clockViewAnimationTest {
    //adjust anchor points
    hourHand = [[UIImageView alloc] init];
    hourHand.frame = CGRectMake(100.0, 100.0f, 2.f, 60.0f);
    hourHand.backgroundColor = [UIColor redColor];
    hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
    [self.view addSubview:hourHand];
    
    minuteHand = [[UIImageView alloc] init];
    minuteHand.frame = CGRectMake(100.0, 100.0f, 2.f, 60.0f);
    minuteHand.backgroundColor = [UIColor blueColor];
    minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
    [self.view addSubview:minuteHand];
    
    secondHand = [[UIImageView alloc] init];
    secondHand.frame = CGRectMake(100.0, 100.0f, 2.f, 60.0f);
    secondHand.backgroundColor = [UIColor greenColor];
    secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
    [self.view addSubview:secondHand];
    //start timer
    timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tickAnimation) userInfo:nil repeats:YES];
    //set initial hand positions
    [self updateHandsAnimated:NO];
}

- (void)tickAnimation {
    [self updateHandsAnimated:YES];
}

- (void)updateHandsAnimated:(BOOL)animated {
    //convert time to hours, minutes and seconds
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
    NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
    CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;
    //calculate hour hand angle //calculate minute hand angle
    CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
    //calculate second hand angle
    CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;
    //rotate hands
    [self setAngle:hourAngle forHand:hourHand animated:animated];
    [self setAngle:minuteAngle forHand:minuteHand animated:animated];
    [self setAngle:secondAngle forHand:secondHand animated:animated];
}
/*
- (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated {
    //generate transform
    CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
    if (animated) {
        // create transform animation
        CABasicAnimation *animation = [CABasicAnimation animation];
        [self updateHandsAnimated:NO];
        animation.keyPath = @"transform";
        animation.toValue = [NSValue valueWithCATransform3D:transform];
        animation.duration = 0.5;
        animation.delegate = self;
        [animation setValue:handView forKey:@"handView"];
        [handView.layer addAnimation:animation forKey:nil];
    } else {
        //set transform directly
        handView.layer.transform = transform;
    }
}
 */
/*
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag {
    //set final position for hand view
    UIView *handView = [anim valueForKey:@"handView"];
    handView.layer.transform = [anim.toValue CATransform3DValue];
}
 */

关键帧动画

//CAKeyframeAnimation同样是CAPropertyAnimation的一个子类,它依然作用于单一的一个属性,但是和CABasicAnimation不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。

- (void)keyframeAnimationTest {
    layerView  = [[UIView alloc] init];
    layerView.frame = CGRectMake(0, 20, 320, 320);
    [self.view addSubview:layerView];
    //add change color button
    UIButton *changeColorBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    changeColorBtn.frame = CGRectMake(50, 200, 200, 44.0);
    [changeColorBtn setTitle:@"change Color" forState:(UIControlStateNormal)];
    [changeColorBtn addTarget:self action:@selector(keyAnimationChangeColor) forControlEvents:UIControlEventTouchUpInside];
    changeColorBtn.backgroundColor = [UIColor redColor];
    [layerView addSubview:changeColorBtn];
    
    //create sublayer
    colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0f);
    colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add it to our view
    [layerView.layer addSublayer:colorLayer];
    
}
- (void)keyAnimationChangeColor {
    //create a key frame animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.duration = 2.0f;
    animation.values = @[
                         //为了动画的平滑特性,我们需要开始和结束的关键帧来匹配当前属性的值
                         (__bridge id)[UIColor blueColor].CGColor,//不能自动把当前值作为第一帧
                         (__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor,
                         (__bridge id)[UIColor blueColor].CGColor,
                         ];
    //apply animation to layer
    [colorLayer addAnimation:animation forKey:nil];
}

//CGPath
- (void)keyframeAnimationPathTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 0.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    //create a path
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [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 = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [containerView.layer addSublayer:pathLayer];
    
    //add the ship
    CALayer *starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 64, 64);
    starLayer.position = CGPointMake(0, 150);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
    
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 4.0;
    animation.path = bezierPath.CGPath;
    //指向曲线切线的方向
    animation.rotationMode = kCAAnimationRotateAuto;
    [starLayer addAnimation:animation forKey:nil];
}

虚拟属性

- (void)valueRotationTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 0.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    //add the ship
    CALayer *starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 128, 128);
    starLayer.position = CGPointMake(150, 150);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
    
    //animate the star rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform";
    animation.duration = 2.0;
    //从M_PI(180度)调整到2 * M_PI(360度),然后运行程序,会发现这时候飞船完全不动了
    //用byValue而不是toValue, 没有做任何旋转,这是因为变换矩阵不能像角度值那样叠加
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 0, 1)];
    [starLayer addAnimation:animation forKey:nil];
}

//transform.rotation属性有一个奇怪的问题是它其实并不存在。这是因为CATransform3D并不是一个对象,它实际上是一个结构体,也没有符合KVC相关属性,transform.rotation实际上是一个CALayer用于处理动画变换的虚拟属性。
//当你对他们做动画时,Core Animation自动地根据通过CAValueFunction来计算的值来更新transform属性。
//CAValueFunction用于把我们赋给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransform3D矩阵值。你可以通过设置CAPropertyAnimation的valueFunction属性来改变,于是你设置的函数将会覆盖默认的函数。

//用transform.rotation而不是transform做动画的好处如下:
//我们可以不通过关键帧一步旋转多于180度的动画。
//可以用相对值而不是绝对值旋转(设置byValue而不是toValue)。
//可以不用创建CATransform3D,而是使用一个简单的数值来指定角度。
//不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性)。

- (void)virtualValueRotationTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 0.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    //add the ship
    CALayer *starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 128, 128);
    starLayer.position = CGPointMake(150, 150);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
    
    //animate the star rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = 2.0;
    animation.toValue = @(M_PI *2);
    [starLayer addAnimation:animation forKey:nil];
}

动画组

//CAAnimationGroup是另一个继承于CAAnimation的子类,它添加了一个animations数组的属性,用来组合别的动画。

- (void)animationGroupTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 0.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    //create a path
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [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 = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [containerView.layer addSublayer:pathLayer];
    
    //add a colored layer
    colorLayer = [CALayer layer];
    colorLayer.frame = CGRectMake(0, 0, 64, 64);
    colorLayer.backgroundColor = [UIColor greenColor].CGColor;
    [containerView.layer addSublayer:colorLayer];
    
    //create the position animation
    CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
    animation1.keyPath = @"position";
    animation1.path = bezierPath.CGPath;
    animation1.rotationMode = kCAAnimationRotateAuto;
    //create the color animation
    CABasicAnimation *animation2 = [CABasicAnimation animation];
    animation2.keyPath = @"backgroundColor";
    animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
    //create the animation group
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[animation1, animation2];
    groupAnimation.duration = 4.0;
    //add the animation to the color layer
    [colorLayer addAnimation:groupAnimation forKey:nil];
}

过渡

//CATransition:CAAnimation的子类,有一个type和subtype来标识变换效果。
//type属性是一个NSString类型
//kCATransitionFade:平滑的淡入淡出效果
//kCATransitionMoveIn:从顶部滑动进入
//kCATransitionPush:它创建了一个新的图层,从边缘的一侧滑动进来,把旧图层从另一侧推出去的效果。
//kCATransitionReveal:把原始的图层滑动出去来显示新的外观,而不是把新的图层滑动进入

//subtype
//kCATransitionFromRight
//kCATransitionFromLeft
//kCATransitionFromTop
//kCATransitionFromBottom

- (void)transitionTest {
    imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(0, 20, 320, 320);
    imageView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:imageView];
    
    UIButton *btnSwitchImage = [UIButton buttonWithType:UIButtonTypeCustom];
    btnSwitchImage.frame = CGRectMake(20, 350, 200, 44);
    btnSwitchImage.backgroundColor = [UIColor blueColor];
    [btnSwitchImage setTitle:@"Switch Image" forState:UIControlStateNormal];
    [btnSwitchImage addTarget:self action:@selector(switchImageAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnSwitchImage];
    
    //set up images
    images = @[
               [UIImage imageNamed:@"Meal"],
               [UIImage imageNamed:@"Star"],
               [UIImage imageNamed:@"Meal"],
               [UIImage imageNamed:@"Star"]
               ];
}

- (void)switchImageAction {
    //set up crossfade transition
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionReveal;
    //apply transition to imageView backing layer
    [imageView.layer addAnimation:transition forKey:nil];
    
    //cycle to next image
    UIImage *currentImage = imageView.image;
    NSUInteger index = [images indexOfObject:currentImage];
    index = (index + 1) % [images count];
    imageView.image = images[index];
}

隐式过渡

//当设置了CALayer的content属性的时候,CATransition的确是默认的行为
//对于视图关联的图层,或者是其他隐式动画的行为,这个特性依然是被禁用的,但是对于你自己创建的图层,这意味着对图层contents图片做的改动都会自动附上淡入淡出的动画。

//对图层树的动画
//to appdelegate first and second viewController

//自定义动画
//更奇怪的是苹果通过UIView +transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:方法提供了Core Animation的过渡特性。但是这里的可用的过渡选项和CATransition的type属性提供的常量完全不同。
//UIView过渡方法中options参数可以由如下常量指定:
//UIViewAnimationOptionTransitionFlipFromLeft
//UIViewAnimationOptionTransitionFlipFromRight
//UIViewAnimationOptionTransitionCurlUp
//UIViewAnimationOptionTransitionCurlDown
//UIViewAnimationOptionTransitionCrossDissolve
//UIViewAnimationOptionTransitionFlipFromTop
//UIViewAnimationOptionTransitionFlipFromBottom

- (void)transitionSelfTest {
    imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(0, 20, 320, 320);
    imageView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:imageView];
    
    UIButton *btnSwitchImage = [UIButton buttonWithType:UIButtonTypeCustom];
    btnSwitchImage.frame = CGRectMake(20, 350, 200, 44);
    btnSwitchImage.backgroundColor = [UIColor blueColor];
    [btnSwitchImage setTitle:@"Switch Image" forState:UIControlStateNormal];
    [btnSwitchImage addTarget:self action:@selector(selfSwitchImageAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnSwitchImage];
    
    //set up images
    images = @[
               [UIImage imageNamed:@"Meal"],
               [UIImage imageNamed:@"Star"],
               [UIImage imageNamed:@"Meal"],
               [UIImage imageNamed:@"Star"]
               ];
}

- (void)selfSwitchImageAction {
    [UIView transitionWithView:imageView
                      duration:1.0
                       options:UIViewAnimationOptionTransitionFlipFromLeft
                    animations:^{
                        //cycle to next image
                        UIImage *currentImage = imageView.image;
                        NSUInteger index = [images indexOfObject:currentImage];
                        index = (index + 1) % [images count];
                        imageView.image = images[index];
                    }
                    completion:nil];
}

//对图层做截图还是很简单的。CALayer有一个-renderInContext:方法,可以通过把它绘制到Core Graphics的上下文中捕获当前内容的图片,然后在另外的视图中显示出来。
//为了让事情更简单,我们用UIView -animateWithDuration:completion:方法来实现。虽然用CABasicAnimation可以达到同样的效果,但是那样的话我们就需要对图层的变换和不透明属性创建单独的动画,然后当动画结束的是哦户在CAAnimationDelegate中把coverView从屏幕中移除。
//用renderInContext:创建自定义过渡效果

- (void)performTransition {
    
    //preserve the current view snapshot
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
    //insert snapshot view in front of this one
    UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
    coverView.frame = self.view.bounds;
    [self.view addSubview:coverView];
    //update the view (we'll simply randomize the layer background color)
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    //perform animation (anything you like)
    [UIView animateWithDuration:1.0 animations:^{
        //scale, rotate and fade the view
        CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
        transform = CGAffineTransformRotate(transform, M_PI_2);
        coverView.transform = transform;
        coverView.alpha = 0.0;
        
    } completion:^(BOOL finished) {
        //remove the cover view now we're finished with it
        [coverView removeFromSuperview];
    }];
}

在动画过程中取消动画

//你可以用-addAnimation:forKey:方法中的key参数来在添加动画之后检索一个动画,使用如下方法:
//- (CAAnimation *)animationForKey:(NSString *)key;
//- (void)removeAnimationForKey:(NSString *)key;
//- (void)removeAllAnimations;
//动画在结束之后被自动移除,除非设置removedOnCompletion为NO,如果你设置动画在结束之后不被自动移除,那么当它不需要的时候你要手动移除它;否则它会一直存在于内存中,直到图层被销毁。

- (void)cancelAnimationTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 20.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    UIButton *startBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    startBtn.frame = CGRectMake(0.0, 400, 100, 44.0f);
    [startBtn setTitle:@"Star" forState:UIControlStateNormal];
    [startBtn addTarget:self action:@selector(startAction) forControlEvents:UIControlEventTouchUpInside];
    [startBtn setBackgroundColor:[UIColor blueColor]];
    [self.view addSubview:startBtn];
    
    UIButton *stopBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    stopBtn.frame = CGRectMake(120.0, 400, 100, 44.0f);
    [stopBtn setTitle:@"Stop" forState:UIControlStateNormal];
    [stopBtn addTarget:self action:@selector(stopAction) forControlEvents:UIControlEventTouchUpInside];
    [stopBtn setBackgroundColor:[UIColor blueColor]];
    [self.view addSubview:stopBtn];

    
    //add the star
    starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 128, 128);
    starLayer.position = CGPointMake(150, 150);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
}

- (void)startAction {
    //animate the ship rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = 2.0;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [starLayer addAnimation:animation forKey:@"rotateAnimation"];
}

- (void)stopAction {
    [starLayer removeAnimationForKey:@"rotateAnimation"];
}
/*
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    //log that the animation stopped
    NSLog(@"The animation stopped (finished:%@", flag? @"Yes":@"No");
}
 */

pragma mark -- 图层时间

//CAMediaTiming协议
//CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。

//持续和重复
//duration是一个CFTimeInterval的类型(类似于NSTimeInterval的一种双精度浮点类型),对将要进行的动画的一次迭代指定了时间。
//CAMediaTiming另外还有一个属性叫做repeatCount,代表动画重复的迭代次数。如果duration是2,repeatCount设为3.5(三个半迭代),那么完整的动画时长将是7秒。
//duration和repeatCount默认都是0,这里的0仅仅代表了“默认”,也就是0.25秒和1次

- (void)durationAndRepeatCountTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 20.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    startBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    startBtn.frame = CGRectMake(0.0, 400, 100, 44.0f);
    [startBtn setTitle:@"Star" forState:UIControlStateNormal];
    [startBtn addTarget:self action:@selector(durationStartAction) forControlEvents:UIControlEventTouchUpInside];
    [startBtn setBackgroundColor:[UIColor blueColor]];
    [self.view addSubview:startBtn];
    
    //add the star
    starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 128, 128);
    starLayer.position = CGPointMake(150, 150);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
}

- (void)setControlsEnabled:(BOOL)enbled {
    startBtn.enabled = enbled;
    startBtn.alpha = enbled? 1.0f:0.25;
}

- (void)durationStartAction {
    CFTimeInterval duration = 3.0f;
    float repeatCount = 2.5;
    //animate the star rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = duration;
    animation.repeatCount = repeatCount;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [starLayer addAnimation:animation forKey:@"rotateAnimation"];
    //disable controls
    [self setControlsEnabled:NO];
}

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

//repeatDuration属性:它让动画重复一个指定的时间,而不是指定次数。
//autoreverses的属性:(BOOL类型)在每次间隔交替循环过程中自动回放
//把repeatDuration设置为INFINITY,于是动画无限循环播放,设置repeatCount为INFINITY也有同样的效果

- (void)repeatDurationTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 20.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    //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:@"Star"].CGImage;
    [containerView.layer addSublayer:doorLayer];
    //apply perspective transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    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];
}

相对时间

//beginTime指定了动画开始之前的的延迟时间
//speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。
//timeOffset:增加timeOffset只是让动画快进到某一点;对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始
//timeOffset并不受speed的影响,。所以如果你把speed设为2.0,把timeOffset设置为0.5,那么你的动画将从动画最后结束的地方开始,因为1秒的动画实际上被缩短到了0.5秒。然而即使使用了timeOffset让动画从结束的地方开始,它仍然播放了一个完整的时长,这个动画仅仅是循环了一圈,然后从头开始播放。

- (void)timeOffsetAndSpeedTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 20.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    speedLabel = [[UILabel alloc] init];
    speedLabel.frame = CGRectMake(0.0, 400.0, 100, 40.0f);
    speedLabel.backgroundColor = [UIColor grayColor];
    [self.view addSubview:speedLabel];
    
    speedSlider = [[UISlider alloc] init];
    speedSlider.frame = CGRectMake(0.0, 450, 320, 40);
    speedSlider.backgroundColor = [UIColor purpleColor];
    [speedSlider addTarget:self action:@selector(updateSliders) forControlEvents:UIControlEventValueChanged];

    [self.view addSubview:speedSlider];
    
    
    timeOffsetLabel = [[UILabel alloc] init];
    timeOffsetLabel.frame = CGRectMake(120, 400.0, 100, 40.0f);
    timeOffsetLabel.backgroundColor = [UIColor grayColor];
    [self.view addSubview:timeOffsetLabel];
    
    timeOffsetSlider = [[UISlider alloc] init];
    timeOffsetSlider.frame = CGRectMake(0.0, 500, 320, 40);
    timeOffsetSlider.backgroundColor = [UIColor purpleColor];
    [timeOffsetSlider addTarget:self action:@selector(updateSliders) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:timeOffsetSlider];
    
    startBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    startBtn.frame = CGRectMake(0.0, 550, 100, 44.0f);
    [startBtn setTitle:@"Star" forState:UIControlStateNormal];
    [startBtn addTarget:self action:@selector(playAction) forControlEvents:UIControlEventTouchUpInside];
    [startBtn setBackgroundColor:[UIColor blueColor]];
    [self.view addSubview:startBtn];
    
    //create a path
    bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(0, 150)];
    [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 = bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [containerView.layer addSublayer:pathLayer];
    
    //add the star
    starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 64, 64);
    starLayer.position = CGPointMake(0, 150);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
    //set initial values
    [self updateSliders];
}

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

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

fillMode

//一种可能是属性和动画没被添加之前保持一致,也就是在模型图层定义的值(见第七章“隐式动画”,模型图层和呈现图层的解释)。
//另一种可能是保持动画开始之前那一帧,或者动画结束之后的那一帧。这就是所谓的填充,因为动画开始和结束的值用来填充开始之前和结束之后的时间。
//需要把removeOnCompletion设置为NO,另外需要给动画添加一个非空的键,于是可以在不需要动画的时候把它从图层上移除。
//fillMode:NSString类型
//kCAFillModeForwards
//kCAFillModeBackwards
//kCAFillModeBoth
//kCAFillModeRemoved 默认

层级关系时间

//对图层调整时间将会影响到它本身和子图层的动画,但不会影响到父图层。另一个相似点是所有的动画都被按照层级组合(使用CAAnimationGroup实例)
//对CALayer或者CAGroupAnimation调整duration和repeatCount/repeatDuration属性并不会影响到子动画。但是beginTime,timeOffset和speed属性将会影响到子动画。

全局时间和本地时间

//全局时间
//CACurrentMediaTime函数来访问马赫时间:
//CFTimeInterval time = CACurrentMediaTime();//它返回了设备自从上次启动后的秒数,并不是你所关心的,它真实的作用在于对动画的时间测量提供了一个相对值。注意当设备休眠的时候马赫时间会暂停,也就是所有的CAAnimations(基于马赫时间)同样也会暂停。
//每个CALayer和CAAnimation实例都有自己本地时间的概念,是根据父图层/动画层级关系中的beginTime,timeOffset和speed属性计算。就和转换不同图层之间坐标关系一样,CALayer同样也提供了方法来转换不同图层之间的本地时间。如下:
//- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
//- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
//当用来同步不同图层之间有不同的speed,timeOffset和beginTime的动画,这些方法会很有用。

暂停,倒回和快进

//可以利用CAMediaTiming来暂停图层本身
//如果把图层的speed设置成0,它会暂停任何添加到图层上的动画。类似的,设置speed大于1.0将会快进,设置成一个负值将会倒回动画。
//self.window.layer.speed = 100;

手动动画

//timeOffset一个很有用的功能在于你可以它可以让你手动控制动画进程,通过设置speed为0,可以禁用动画的自动播放,然后来使用timeOffset来来回显示动画序列。这可以使得运用手势来手动控制动画变得很简单。

- (void)handleAnimationTest {
    containerView = [[UIView alloc] init];
    containerView.frame = CGRectMake(0.0, 20.0, 320, 320);
    containerView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:containerView];
    
    //add the door
    starLayer = [CALayer layer];
    starLayer.frame = CGRectMake(0, 0, 128, 256);
    starLayer.position = CGPointMake(150-64, 150);
    starLayer.anchorPoint = CGPointMake(0, 0.5);
    starLayer.contents = (__bridge id)[UIImage imageNamed:@"Star"].CGImage;
    [containerView.layer addSublayer:starLayer];
    
    //apply perspective transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    containerView.layer.sublayerTransform = perspective;
    
    //add pan gesture recognizer to handle swipes
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
    [pan addTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
    
    //pause all layer animations
    starLayer.speed = 0.0;
    //apply swinging animation (which won't play because layer is paused)
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation.y";
    animation.toValue = @(-M_PI_2);
    animation.duration = 2.0;
    animation.repeatDuration = INFINITY;
    animation.autoreverses = YES;
    [starLayer addAnimation:animation forKey:nil];
}

- (void)pan:(UIPanGestureRecognizer *)pan {
    //get horizontal component of pan gesture
    CGFloat x = [pan translationInView:self.view].x;
    //conver from points to animation duration //using a reasonable scale factor
    x /= 200.0f;
    //update timeOffset and clamp result
    CFTimeInterval timeOffset = starLayer.timeOffset;
    timeOffset = MIN(0.999, MAX(0.0, timeOffset - x));
    starLayer.timeOffset = timeOffset;
    //reset pan gesture
    [pan setTranslation:CGPointZero inView:self.view];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,509评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,113评论 5 13
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,106评论 1 23
  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 2,299评论 0 6
  • Core Animation Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,...
    45b645c5912e阅读 3,032评论 0 21