Cowboy Tech

设计复杂的iOS动画效果

动画效果的UIView Object

// 显示动画
- (void)showWithDuration:(CGFloat)duration animated:(BOOL)animated;

// 隐藏动画
- (void)hideWithDuration:(CGFloat)duration animated:(BOOL)animated;

// 创建view
- (void)buildView;

// 动画百分比(手动设置动画的程度)
- (void)percent:(CGFloat)percent;

制定统一的动画接口

即:相关有动画效果的类,有同一的动画方法命名

  1. 为了实现后续复杂的动画组合
  2. 后续的代码维护极为方便
  3. 优先考虑里氏代换原则

动画中的高内聚低耦合原理

  1. 高内聚:有动画效果的类,自身具有动画方法
  2. 不要把实现动画的细节暴露在外
  3. 设计动画类尽量要符合单一职能原则,以便后续方便组合成复杂的动画效果

设计动画函数的注意事项

  1. 动画方法的命名统一
  2. 预留非动画情形的设计(tableView等reuse情况)
  3. 用百分比来表示动画的执行程度
  4. 懒加载的使用

动画效果就是frame值的重新设定

[UIView animateWithDuration:duration animations:^{
        self.frame = self.midRect;
        self.alpha = 1.f;
    }];

一个具有动画效果的类

#import "LineView.h"

@interface LineView ()
@property (nonatomic) CGRect startRect;
@property (nonatomic) CGRect midRect;
@property (nonatomic) CGRect endRect;
@end

@implementation LineView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
    self.alpha = 0.f;
}
return self;
}

// 创建view
- (void)buildView {
// todo
self.startRect = self.frame;
self.midRect   = CGRectMake(self.startRect.origin.x + self.offsetX,
                            self.startRect.origin.y,
                            self.startRect.size.width,
                            self.startRect.size.height);
self.endRect   = CGRectMake(self.startRect.origin.x + self.offsetX * 2,
                            self.startRect.origin.y,
                            self.startRect.size.width,
                            self.startRect.size.height);
                        }


// 显示动画
- (void)showWithDuration:(CGFloat)duration animated:(BOOL)animated {
if (animated == YES) {
    [UIView animateWithDuration:duration animations:^{
        self.frame = self.midRect;
        self.alpha = 1.f;
    }];
} else {
    self.frame = self.midRect;
    self.alpha = 1.f;
}
}

// 隐藏动画
- (void)hideWithDuration:(CGFloat)duration animated:(BOOL)animated {
if (animated == YES) {
    [UIView animateWithDuration:duration animations:^{
        self.frame = self.endRect;
        self.alpha = 0.f;
    } completion:^(BOOL finished) {
        self.frame = self.startRect;
    }];
} else {
    self.frame = self.startRect;
    self.alpha = 0.f;
}
}



// 动画百分比(手动设置动画的程度)
- (void)percent:(CGFloat)percent {
CGFloat tmpOffsetX = 0;

if (percent <= 0) {
    tmpOffsetX = 0;
} else if (percent >= 1) {
    tmpOffsetX = self.offsetX;
} else {
    tmpOffsetX = percent * self.offsetX;
}

self.frame = CGRectMake(self.startRect.origin.x + tmpOffsetX,
                        self.startRect.origin.y,
                        self.startRect.size.width,
                        self.startRect.size.height);
}

里氏代换原则处理动画类的继承问题

1
2
SourceView *tmpView = [[ChildTwoView alloc] init];
[tmpView show];
  1. 里氏代换原则的基本原理 (多态)
  2. 设计中要确保父类可以直接调用子类的方法
  3. 将父类设计成虚类

动画中的模块化设计

  1. 动画效果实现难度的判断
  2. 将看到的动画效果拆分成小模块
  3. 将写好的小模块组合成你所需要的动画效果

ViewController.m

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

// 复杂的动画被写进了BaseAnimationView当中,没有暴露不必要的细节
BaseAnimationView *baseView = [[BaseAnimationView alloc] initWithFrame:CGRectZero];
[self.view addSubview:baseView];
[baseView show];
}

BaseAnimation.m

- (void)show {
[self.circleView show];
[self.lineView show];
}

- (void)hide {
[self.circleView hide];
[self.lineView hide];
}

- (void)buildView {
self.circleView = [[CircleView alloc] initWithFrame:CGRectZero];
[self addSubview:self.circleView];

self.lineView = [[RectView alloc] initWithFrame:CGRectZero];
[self addSubview:self.lineView];
}

circleView.m / lineView.m

- (void)show {}
- (void)hide {}
- (void)buildView {}

延时执行某方法

[self performSelector:@selector(excuteAfterDelay) withObject:nil afterDelay:6];

初始化UIView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
    self.rect = frame;
}
return self;
}