一、案例:
我们这里初始按钮是一个很小的按钮,点击就不断放大,最大就放大到全屏幕。
核心代码如下:
@interface TotalUpdateController ()
@property (nonatomic, strong) UIView *purpleView;
@property (nonatomic, strong) UIView *orangeView;
@property (nonatomic, assign) BOOL isExpaned;
@end
@implementation TotalUpdateController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *purpleView = [[UIView alloc] init];
purpleView.backgroundColor = UIColor.purpleColor;
purpleView.layer.borderColor = UIColor.blackColor.CGColor;
purpleView.layer.borderWidth = 2;
[self.view addSubview:purpleView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)
];
[purpleView addGestureRecognizer:tap];
self.purpleView = purpleView;
UIView *orangeView = UIView.new;
orangeView.backgroundColor = UIColor.orangeColor;
orangeView.layer.borderColor = UIColor.blackColor.CGColor;
orangeView.layer.borderWidth = 2;
[self.view addSubview:orangeView];
self.orangeView = orangeView;
// 这里,我们不使用updateViewConstraints方法,但是我们一样可以做到。
// 不过苹果推荐在updateViewConstraints方法中更新或者添加约束的
[self updateWithExpand:NO animated:NO];
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0;
label.textColor = [UIColor redColor];
label.font = [UIFont systemFontOfSize:16];
label.textAlignment = NSTextAlignmentCenter;
label.text = @"点击purple部分放大,orange部分最大值250,最小值90";
[self.purpleView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(0);
make.left.right.mas_equalTo(0);
}];
}
- (void)updateWithExpand:(BOOL)isExpanded animated:(BOOL)animated {
self.isExpaned = isExpanded;
[self.purpleView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(20);
make.right.mas_equalTo(-20);
if (isExpanded) {
make.bottom.mas_equalTo(-20);
} else {
make.bottom.mas_equalTo(-300);
}
}];
[self.orangeView mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.purpleView);
// 这里使用优先级处理
// 设置其最大值为250,最小值为90
if (!isExpanded) {
make.width.height.mas_equalTo(100 * 0.5).priorityLow();
} else {
make.width.height.mas_equalTo(100 * 3).priorityLow();
}
// 最大值为250
make.width.height.lessThanOrEqualTo(@250);
// 最小值为90
make.width.height.greaterThanOrEqualTo(@90);
}];
if (animated) {
[self.view setNeedsUpdateConstraints];
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
}
}
- (void)onTap {
[self updateWithExpand:!self.isExpaned animated:YES];
}
@end
二、核心代码讲解:
想要更新约束时添加动画,就需要调用关键的一行代码:setNeedsUpdateConstraints ,这是选择对应的视图中的约束需要更新。
对于updateConstraintsIfNeeded 这个方法并不是必须的,但是有时候不调用就无法起到我们的效果。但是,官方都是这么写的,从约束的更新原理上讲,这应该写上。我们要使约束立即生效,就必须调用layoutIfNeeded 此方法。看下面的方法,就是动画更新约束的核心代码:
// 告诉self.view约束需要更新
[self.view setNeedsUpdateConstraints];
// 调用此方法告诉self.view检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
三、知识点:
为什么我们在动画里调用[self.view layoutIfNeeded]才能实现动画效果?如果我们按照以下的方式,能否实现动画效果吗?想法很简单,就是将我们的约束在动画里面做,代码如下:
[UIView animateWithDuration:0.3 animations:^{
[self.orangeView mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.purpleView);
// 这里使用优先级处理
// 设置其最大值为250,最小值为90
if (!isExpanded) {
make.width.height.mas_equalTo(100 * 0.5).priorityLow();
} else {
make.width.height.mas_equalTo(100 * 3).priorityLow();
}
// 最大值为250
make.width.height.lessThanOrEqualTo(@250);
// 最小值为90
make.width.height.greaterThanOrEqualTo(@90);
}];
}];
答案是不能的,根本不会出现动画效果。
要想整明白这一点,那么就要明白我们设置的动画是在哪一个阶段产生的,其实:
iOS 动画 的渲染也是基于上述 Core Animation 流水线完成的。这里我们重点关注app 与 Render Server 的执行流程。
日常开发中,如果不是特别复杂的动画,一般使用 UIView Animation 实现,iOS 将其处理过程分为如下三部阶段:
Step 1: 调用animationWithDuration:animations: 方法 -Step 2: 在Animation Block 中进行Layout,Display,Prepare,Commit 等步骤。Step 3: Render Server 根据 Animation 逐帧进行渲染。
这里只进行一次将图层树 和CAAnimation对象 提交到渲染服务进程 ,然后由渲染服务进程 根据CAAnimation参数 和图层树 信息,去渲染动画过程中的每一帧。
所以,当我们在动画块里进行约束,只是进行了约束,而没有真正的参与到渲染中来;而我们要想产生动画,那一定是要求确定其Layout属性才进行渲染的。所以,当我们调用LayoutIfNeeded之后,才能将我们的约束提交到renderServer中。下面是LayoutIfNeeded的文档描述:
Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.
大家可以参考:github 下载源代码:MasonryDemo |