实现动画方式深度解析(十八) —— UIView动画 (一)

版本记录

版本号 时间
V1.0 2017.09.24

前言

app中好的炫的动画可以让用户耳目一新,为产品增色不少,关于动画的实现我们可以用基本动画、关键帧动画、序列帧动画以及基于CoreGraphic的动画等等,接下来这几篇我就介绍下我可以想到的几种动画绘制方法。具体Demo示例已开源到Github —— 刀客传奇,感兴趣的可以看我写的另外几篇。
1. 实现动画方式深度解析(一) —— 播放GIF动画(一)
2. 实现动画方式深度解析(二) —— 播放GIF动画之框架FLAnimatedImage的使用(二)
3. 实现动画方式深度解析(三) —— 播放序列帧动画(一)
4. 实现动画方式深度解析(四) —— QuartzCore框架(一)
5. 实现动画方式深度解析(五) —— QuartzCore框架之CoreAnimation(二)
6. 实现动画方式深度解析(六) —— Core Animation Basics(三)
7. 实现动画方式深度解析(七) —— Core Animation之Setting Up Layer Objects(四)
8. 实现动画方式深度解析(八) —— Core Animation之动画层内容 (五)
9. 实现动画方式深度解析(九) —— Core Animation之构建图层层级 (六)
10. 实现动画方式深度解析(十) —— Core Animation之高级动画技巧 (七)
11. 实现动画方式深度解析(十一) —— Core Animation之更改图层的默认行为(八)
12. 实现动画方式深度解析(十二) —— Core Animation之提高动画的性能(九)
13. 实现动画方式深度解析(十三) —— Core Animation之图层样式属性动画(十)
14. 实现动画方式深度解析(十四) —— Core Animation之 KVC 扩展(十一)
15. 实现动画方式深度解析(十五) —— Core Animation之可动画属性 (十二)
16. 实现动画方式深度解析(十六) —— Core Animation之CABasicAnimation动画示例 (十三)
17. 实现动画方式深度解析(十七) —— Core Animation之CAKeyframeAnimation动画示例 (十四)

UIView动画

UIView动画也是系统提供的一种动画实现方式,这种方式是实现很简单。UIView动画可以看做是对Core Animation的封装。

UIView可以进行动画属性如下:

  • frame
  • bounds
  • center
  • transform
  • alpha
  • backgroundColor
  • contentStretch

下面我们看一下UIView提供了有关动画的接口。

@interface UIView(UIViewAnimation)

+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;  // additional context info passed to will start/did stop selectors. begin/commit can be nested
+ (void)commitAnimations;                                                 // starts up any animations when the top level animation is commited

// no getters. if called outside animation block, these setters have no effect.
+ (void)setAnimationDelegate:(nullable id)delegate;                          // default = nil
+ (void)setAnimationWillStartSelector:(nullable SEL)selector;                // default = NULL. -animationWillStart:(NSString *)animationID context:(void *)context
+ (void)setAnimationDidStopSelector:(nullable SEL)selector;                  // default = NULL. -animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
+ (void)setAnimationDuration:(NSTimeInterval)duration;              // default = 0.2
+ (void)setAnimationDelay:(NSTimeInterval)delay;                    // default = 0.0
+ (void)setAnimationStartDate:(NSDate *)startDate;                  // default = now ([NSDate date])
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;              // default = UIViewAnimationCurveEaseInOut
+ (void)setAnimationRepeatCount:(float)repeatCount;                 // default = 0.0.  May be fractional
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;    // default = NO. used if repeat count is non-zero
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;  // default = NO. If YES, the current view position is always used for new animations -- allowing animations to "pile up" on each other. Otherwise, the last end state is used for the animation (the default).

+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;  // current limitation - only one per begin/commit block

+ (void)setAnimationsEnabled:(BOOL)enabled;                         // ignore any attribute changes while set.
#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) BOOL areAnimationsEnabled;
#else
+ (BOOL)areAnimationsEnabled;
#endif
+ (void)performWithoutAnimation:(void (NS_NOESCAPE ^)(void))actionsWithoutAnimation NS_AVAILABLE_IOS(7_0);

#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) NSTimeInterval inheritedAnimationDuration NS_AVAILABLE_IOS(9_0);
#else
+ (NSTimeInterval)inheritedAnimationDuration NS_AVAILABLE_IOS(9_0);
#endif

@end


@interface UIView(UIViewAnimationWithBlocks)

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0, completion = NULL

/* Performs `animations` using a timing curve described by the motion of a spring. When `dampingRatio` is 1, the animation will smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will oscillate more and more before coming to a complete stop. You can use the initial spring velocity to specify how fast the object at the end of the simulated spring was moving before it was attached. It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a second. So if you're changing an object's position by 200pt in this animation, and you want the animation to behave as if the object was moving at 100pt/s before the animation started, you'd pass 0.5. You'll typically want to pass 0 for the velocity. */ 
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview

/* Performs the requested system-provided animation on one or more views. Specify addtional animations in the parallelAnimations block. These additional animations will run alongside the system animation with the same timing and duration that the system animation defines/inherits. Additional animations should not modify properties of the view on which the system animation is being performed. Not all system animations honor all available options.
 */
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

@end


@interface UIView (UIViewKeyframeAnimations)

+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations NS_AVAILABLE_IOS(7_0); // start time and duration are values between 0.0 and 1.0 specifying time and duration relative to the overall time of the keyframe animation

@end

UIView 动画几个重要接口

下面我们就看一下几个重要的接口。

1. 动画开始

+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;  
// additional context info passed to will start/did stop selectors. begin/commit can be nested

2. 结束动画标记

+ (void)commitAnimations; 

3. 设置动画时间

+ (void)setAnimationDuration:(NSTimeInterval)duration; 

4. 设置动画代理

+ (void)setAnimationDelegate:(nullable id)delegate;

5. 设置动画将开始时代理对象执行的SEL

+ (void)setAnimationWillStartSelector:(nullable SEL)selector; 

6. 设置动画结束时代理对象执行的SEL

+ (void)setAnimationDidStopSelector:(nullable SEL)selector; 

7. 设置动画延迟执行的时间

+ (void)setAnimationDelay:(NSTimeInterval)delay;  

8. 设置动画的重复次数

+ (void)setAnimationRepeatCount:(float)repeatCount; 

9. 设置动画的曲线

+ (void)setAnimationCurve:(UIViewAnimationCurve)curve; 

这里有一个枚举值,如下所示:

typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
    UIViewAnimationCurveEaseInOut,         // slow at beginning and end
    UIViewAnimationCurveEaseIn,            // slow at beginning
    UIViewAnimationCurveEaseOut,           // slow at end
    UIViewAnimationCurveLinear,
};

10. 设置是否从当前状态开始播放动画

+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState; 

 // default = NO. If YES, the current view position is always used for new animations -- allowing animations to "pile up" on each other. Otherwise, the last end state is used for the animation (the default).

假设上一个动画正在播放,且尚未播放完毕,我们将要进行一个新的动画:

  • 当为YES时:动画将从上一个动画所在的状态开始播放
  • 当为NO时:动画将从上一个动画所指定的最终状态开始播放(此时上一个动画马上结束)。

11. 设置动画是否继续执行相反的动画

+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;   

 // default = NO. used if repeat count is non-zero

12. 是否禁用动画效果

对象属性依然会被改变,只是没有动画效果。

+ (void)setAnimationsEnabled:(BOOL)enabled; 

13. 设置视图的过渡效果

+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache; 

 // current limitation - only one per begin/commit block

这里有一个枚举UIViewAnimationTransition,如下所示:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

14. block动画

有关UIView的block动画,全部在UIView的一个分类UIViewAnimationWithBlocks里面。

@interface UIView(UIViewAnimationWithBlocks)

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0, completion = NULL

/* Performs `animations` using a timing curve described by the motion of a spring. When `dampingRatio` is 1, the animation will smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will oscillate more and more before coming to a complete stop. You can use the initial spring velocity to specify how fast the object at the end of the simulated spring was moving before it was attached. It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a second. So if you're changing an object's position by 200pt in this animation, and you want the animation to behave as if the object was moving at 100pt/s before the animation started, you'd pass 0.5. You'll typically want to pass 0 for the velocity. */ 
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview

/* Performs the requested system-provided animation on one or more views. Specify addtional animations in the parallelAnimations block. These additional animations will run alongside the system animation with the same timing and duration that the system animation defines/inherits. Additional animations should not modify properties of the view on which the system animation is being performed. Not all system animations honor all available options.
 */
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

@end

UIView 动画简单示例

下面我们就看一下UIView动画实现的简单示例。

1. 简单动画

我们先看一下简单的动画示例。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIView *animationView;

@end

@implementation ViewController

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //UI
    [self setupUI];
    
    //UIView动画
    [self demoPropertyUIViewAnimation];
}

#pragma mark - Object Private Function

- (void)demoPropertyUIViewAnimation
{
    [UIView beginAnimations:@"animation" context:nil];
    [UIView setAnimationDuration:3.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationWillStartSelector:@selector(startAnimation)];
    [UIView setAnimationDidStopSelector:@selector(stopAnimation)];
    [UIView setAnimationRepeatCount:10];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    self.animationView.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
    [UIView commitAnimations];
}

- (void)setupUI
{
    self.view.backgroundColor = [UIColor lightGrayColor];
    
    self.animationView = [[UIView alloc] initWithFrame:CGRectMake(250.0, 250.0, 200.0, 200.0)];
    [self.view addSubview:self.animationView];
    self.animationView.backgroundColor = [UIColor redColor];
}

#pragma mark - Action && Notification

- (void)startAnimation
{
    NSLog(@"开始动画");
}

- (void)stopAnimation
{
    NSLog(@"停止动画");
}

@end

下面我们看一下输出以及动画效果。

2017-09-24 20:22:24.254 JJUIViewAnimation_demo9[3301:274498] 开始动画
2017-09-24 20:22:54.252 JJUIViewAnimation_demo9[3301:274498] 停止动画
实现动画方式深度解析(十八) —— UIView动画 (一)_第1张图片

2. block动画

ios 4.0以后出现的block动画,这种动画也是我们工程中常用的动画形式。

  • 最简洁的Block动画:包含时间和动画
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); 

// delay = 0.0, options = 0, completion = NULL
  • 带有动画完成回调的block动画
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

 // delay = 0.0, options = 0
  • 带有延迟时间和过度效果的动画
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

这里有一个枚举值UIViewAnimationOptions,它就是描述过度效果的枚举值。

 UIViewAnimationOptionLayoutSubviews            //进行动画时布局子控件
 UIViewAnimationOptionAllowUserInteraction      //进行动画时允许用户交互
 UIViewAnimationOptionBeginFromCurrentState     //从当前状态开始动画
 UIViewAnimationOptionRepeat                    //无限重复执行动画
 UIViewAnimationOptionAutoreverse               //执行动画回路
 UIViewAnimationOptionOverrideInheritedDuration //忽略嵌套动画的执行时间设置
 UIViewAnimationOptionOverrideInheritedCurve    //忽略嵌套动画的曲线设置
 UIViewAnimationOptionAllowAnimatedContent      //转场:进行动画时重绘视图
 UIViewAnimationOptionShowHideTransitionViews   //转场:移除(添加和移除图层的)动画效果
 UIViewAnimationOptionOverrideInheritedOptions  //不继承父动画设置

 UIViewAnimationOptionCurveEaseInOut            //时间曲线,慢进慢出(默认值)
 UIViewAnimationOptionCurveEaseIn               //时间曲线,慢进
 UIViewAnimationOptionCurveEaseOut              //时间曲线,慢出
 UIViewAnimationOptionCurveLinear               //时间曲线,匀速

 UIViewAnimationOptionTransitionNone            //转场,不使用动画
 UIViewAnimationOptionTransitionFlipFromLeft    //转场,从左向右旋转翻页
 UIViewAnimationOptionTransitionFlipFromRight   //转场,从右向左旋转翻页
 UIViewAnimationOptionTransitionCurlUp          //转场,下往上卷曲翻页
 UIViewAnimationOptionTransitionCurlDown        //转场,从上往下卷曲翻页
 UIViewAnimationOptionTransitionCrossDissolve   //转场,交叉消失和出现
 UIViewAnimationOptionTransitionFlipFromTop     //转场,从上向下旋转翻页
 UIViewAnimationOptionTransitionFlipFromBottom  //转场,从下向上旋转翻页

下面我们还是先看一下示例代码。

- (void)demoBlockUIViewAnimation
{
    self.animationView.frame = CGRectMake(50.0, 250.0, 200.0, 200.0);
    
    [UIView animateWithDuration:3.0 animations:^{
        self.animationView.backgroundColor = [UIColor blueColor];
    } completion:^(BOOL finished) {
        NSLog(@"动画完成了");
    }];
}

上面就是一个很简单的示例,非常简单,下面看一下效果。

实现动画方式深度解析(十八) —— UIView动画 (一)_第2张图片

3. 转场动画

下面直接看代码。

- (void)demoTransitionUIViewAnimation
{
    self.animationView.frame = CGRectMake(50.0, 250.0, 200.0, 200.0);

    [UIView beginAnimations:@"Animation" context:nil];
    [UIView setAnimationDuration:3.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationWillStartSelector:@selector(startAnimation)];
    [UIView setAnimationDidStopSelector:@selector(stopAnimation)];
    [UIView setAnimationRepeatCount:5];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.animationView cache:YES];
    self.animationView.backgroundColor = [UIColor blueColor];
    [UIView commitAnimations];
}

转场动画有个参数cache,可以设置为YES或者NO,它们有什么区别呢?

首先把要动画的视图进行截图(此处为何要截图说明一下,因为要动画的视图上边一般都会有好多的控件,如果让动画直接操作视图,那么视图带着自身内部的控件,这样做动画的话会增加系统的负担,从而使动画效率降低,所以为了减轻系统负担,使动画更流畅,才对动画视图进行截图处理已完成动画效果),然后对视图的截图进行动画操作。

  • cache选为YES时:系统只会在动画开始的时候对视图进行截图,然后一直到动画结束,都用开始截的那张图,这种方法好处是减轻系统负担,使动画效果更流畅、更自然。但这样也必会有缺点:就是当动画视图中的控件有变化的时候,不能立即更新在视图上,从而达不到实时更新的效果。

  • cache选为NO时:系统会在动画整个过程中对视图进行实时截图,这样当视图中的控件更新时也会实时显示在动画过程中,弥补了cache选择yes得缺点,但这也把cache选择yes的优点变成缺点,是系统效率变慢,如果动画视图中的控件过多,会让动画看起来不那么自然。

这里还要注意:如果要在转换期间更改视图的外观 - 例如,从一个视图翻转到另一个视图 - 然后使用容器视图(UIView的实例),如下所示:

  • Begin an animation block.
  • Set the transition on the container view.
  • Remove the subview from the container view.
  • Add the new subview to the container view.
  • Commit the animation block.

iOS 4.0及更高版本不鼓励使用此方法。 您应该使用transition(with:duration:options:animations:completion:)
方法来执行转换。

后记

未完,待续~~~

实现动画方式深度解析(十八) —— UIView动画 (一)_第3张图片

你可能感兴趣的:(实现动画方式深度解析(十八) —— UIView动画 (一))