美文网首页IOSbug买不来的iOS实用技巧
CoreAnimation手记(二)显式动画

CoreAnimation手记(二)显式动画

作者: 司马捷 | 来源:发表于2015-11-12 11:11 被阅读110次

    属性动画

    属性动画 最用于图层的某个单一属性,并指定了它的一个目标值,或者一连串要做的动画值.
    属性动画分 基础动画和关键帧动画

    基础动画

    最简单的形式就是从一个值改变到另一个值,这也是CABasicAnimation最主要的功能。

    CABasicAnimation是CAPropertyAnimation的一个子类,而CAPropertyAnimation的父类是CAAnimation

    一.第一个类CABasicAnimation

    笔记: CABasicAnimation 的三个重要属性 id fromValue id toValue id byValue,为了防止冲突,不能一次性同时给这三个值赋值.

    有一个注意的是,设置动画,过度到一个新的颜色,但是动画结束之后,又立刻变回原来的颜色.

    <code><pre>
    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;
    </code></pre>
    第一个解决方案:
    在动画开始之前或者动画结束之后。

    <code><pre>
    CALayer *layer = self.colorLayer.presentationLayer ?:self.colorLayer;

    animation.fromValue = (__bridge id)layer.backgroundColor;

    [CATransaction begin];

    [CATransaction setDisableActions:YES];

    self.colorLayer.backgroundColor = color.CGColor;

    [CATransaction commit];
    </code></pre>

    说明这里了设置图层的颜色属性,禁止了隐式动画,因为这里的图层是独立的,不是UIVIew中得

    <code><pre>
    //这里封装了一个方法替代 setValue animation to value直接applyBasicAnimation 这样,动画完了以后,设置

    的属性变化也会对应修改 这个代码可以复制过去
    - (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 forKeyPath:animation.keyPath];

    [CATransaction commit];

    //apply animation to layer

    [layer addAnimation:animation forKey:nil];
    }

      - (IBAction)changeColor{ 
    

    //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:self.colorLayer];
    }

    </code></pre>

    说明这里 使用keyPath 修改layer的一个属性动画,实际上它是一个关键路径(一些用点表示法可以在层级关系中指向任意嵌套的对象),而不仅仅是一个属性的名称,因为这意味着动画不仅可以作用于图层本身的属性,而且还包含了它的子成员的属性,甚至是一些虚拟的属性(后面会详细解释)。

    第二个解决方案:

    实现CAAnimationDelegate 协议中的delegate

    - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
    {
    //set the backgroundColor property to match animation toValue
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
    [CATransaction commit];
    }
    

    注意:使用委托模式而不是一个完成块会带来一个问题,就是当你有多个动画的时候,无法在在回调方法中区分哪个图层调用的。

    引用:

    不幸的是,即使做了这些,还是有个问题,清单8.4在模拟器上运行的很好,但当真正跑在iOS设备上时,我们发现在-animationDidStop:finished:委托方法调用之前,指针会迅速返回到原始值,这个清单8.3图层颜色发生的情况一样。

    问题在于回调方法在动画完成之前已经被调用了,但不能保证这发生在属性动画返回初始状态之前。这同时也很好地说明了为什么要在真实的设备上测试动画代码,而不仅仅是模拟器。

    我们可以用一个fillMode属性来解决这个问题,下一章会详细说明,这里知道在动画之前设置它比在动画结束之后更新属性更加方便。

    第二个类:CAKeyframeAnimation

    <code><pre>
    @interface ViewController ()

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

    @end

    @implementation ViewController

    - (void)viewDidLoad
    

    {
    [super viewDidLoad];
    //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;
    [self.containerView.layer addSublayer:pathLayer];
    
    //add the ship
    CALayer *shipLayer = [CALayer layer];
    shipLayer.frame = CGRectMake(0, 0, 64, 64);
    shipLayer.position = CGPointMake(0, 150);
    shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:shipLayer];
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 4.0;
    animation.path = bezierPath.CGPath;
    [shipLayer addAnimation:animation forKey:nil];
    

    }

    @end

    </code></pre>

    运行示例,你会发现飞船的动画有些不太真实,这是因为当它运动的时候永远指向右边,而不是指向曲线切线的方向。你可以调整它的affineTransform来对运动方向做动画,但很可能和其它的动画冲突。

    幸运的是,苹果预见到了这点,并且给CAKeyFrameAnimation添加了一个rotationMode的属性。设置它为常量kCAAnimationRotateAuto(清单8.7),图层将会根据曲线的切线自动旋转(图8.2)。

    animation.rotationMode = kCAAnimationRotateAuto;
    

    第三个类 CATransision

    CATransision可以对图层任何变化平滑过渡的事实使得它成为那些不好做动画的属性图层行为的理想候选。苹果当然意识到了这点,并且当设置了CALayer的content属性的时候,CATransition的确是默认的行为。但是对于视图关联的图层,或者是其他隐式动画的行为,这个特性依然是被禁用的,但是对于你自己创建的图层,这意味着对图层contents图片做的改动都会自动附上淡入淡出的动画。

    这段翻译的让我真的是很费解.在这里顺便说明下:

    CATransision 可以对图层上发生的任何变化,(属性或者内容大小),都可以添加一个平滑过渡的动画.CATransition的动画是默认的隐式动画.

    过渡动画和之前的属性动画或者动画组添加到图层上的方式一致,都是通过-addAnimation:forKey:方法。但是和属性动画不同的是,对指定的图层一次只能使用一次CATransition,因此,无论你对动画的键设置什么值,过渡动画都会对它的键设置成“transition”,也就是常量kCATransition。

    过渡动画和属性动画的区别,两个动画的添加方式一直,不过一个是默认的值,(transition),一个可以自定义.

    一段神奇的代码

    <code><pre>

    import "AppDelegate.h"

    import "FirstViewController.h"

    import "SecondViewController.h"

    import <QuartzCore/QuartzCore.h>

    @implementation AppDelegate
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {

    self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];

    UIViewController *viewController1 = [[FirstViewController alloc] init];
    UIViewController *viewController2 = [[SecondViewController alloc] init];
    self.tabBarController = [[UITabBarController alloc] init];
    self.tabBarController.viewControllers = @[viewController1, viewController2];
    self.tabBarController.delegate = self;
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
    

    }
    - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
    {

     //set up crossfade transition
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionFade;
    //apply transition to tab bar controller's view
    [self.tabBarController.view.layer addAnimation:transition forKey:nil];
    

    }
    @end

    </code></pre>

    UIVIew提供的过渡动画函数

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

    一个自定义过渡动画的代码

    <code><pre>
    @implementation ViewController
    - (IBAction)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];
    }];
    

    }

    </code></pre>

    在动画过程中取消动画

    添加动画
    - (CAAnimation *)animationForKey:(NSString *)key;

    删除动画
    - (void)removeAnimationForKey:(NSString *)key;

    或者移除所有动画:

     - (void)removeAllAnimations;

    相关文章

      网友评论

        本文标题:CoreAnimation手记(二)显式动画

        本文链接:https://www.haomeiwen.com/subject/nzyhhttx.html