今天来点硬货! 先看我们今天要实现的动画:
好的,在Autolayout没出现以前,也就是iOS6以前,要实现这个效果非常复杂,因为你要实时地去算每个视图的位置,而且还很难保持联动。但是iOS6中出现了Autolayout出现,再来看这个效果就豁然开朗了。而且随着iPhone、iPad的多种尺寸屏幕的趋势,势必推送SizeClass的大面积应用,而SizeClass的使用又和Autolayout密不可分。所以从某种意义上来说,多种尺寸屏幕的来临已经宣告了Autolayout时代的到来。所以,结论就是,iOS开发者必须告别手写代码,忘记frame,彻底拥抱Autolayout了。这也是为什么我之前花了那么多篇文章介绍Autolayout的不同方面使用方法的原因,因为它真的很重要。
今天,我们来讲Autolayout很重要的一方面。我们都知道,既然使用了Autolayout,也就是视图、控件的位置已经被约束(Constraint)限制了。比如一个图片视图(UIImageView)被约束在了离顶端30px,离左侧30px,宽60px,高60px
的位置,那么它的位置就已经确定了。无论怎么旋转屏幕或者在什么尺寸的设备上都是在离顶端30px,离左侧30px,宽60px,高60px
。这真的很棒,不是吗?但是你肯定发现问题了,如果我们要改变视图的位置或者使用动画(Core Animation)该怎么办呢?以前,我们就是重新设置frame
。但是在Autolayout里面已经没有frame
这个概念了(不信可以打印frame看一看哦)。所以我们的思路是:将计就计,重写约束(Constraint)
。
接下来,我们通过实现上面gif中的效果具体看一下怎么使用。
既然使用Autolayout已经成为不可逆的趋势了,那么我们有必要熟悉一下Storyboard的使用。 首先拖四个View上来:
并且设置好约束,从左到右分别是自上而下的五个视图的约束:
然后分别设置五个View的 tag
值为:0,1,2,3,4.
把五个view连接到代码文件中作为属性:
@property (strong, nonatomic) IBOutlet UIView *view1;
@property (strong, nonatomic) IBOutlet UIView *view2;
@property (strong, nonatomic) IBOutlet UIView *view3;
@property (strong, nonatomic) IBOutlet UIView *view4;
@property (strong, nonatomic) IBOutlet UIView *view5;
定义一个数组并把这5个view存进去,方面后面使用:
@property (nonatomic, strong) NSMutableArray *imageViewList;
在viewDidLoad中用循环把五个view的颜色设好,并分别添加上 UITapGestureRecognizer *tap
这个点击的手势,统一触发-(void)Tap:(UITapGestureRecognizer *)gesture
这个方法。
说到动画,不难联想到
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
这个方法。就像FLASH视频制作软件一样,我们只要设置好起始帧
和结束帧
就可以了,之间的过程UIView
会搞定的。同样的道理,对于Autolayout,我们现在已经设置好了起始约束
(就是刚才你在Storyboard中设置的约束),只需要一个结束约束
就行了。那么这里就涉及到了手写约束
了。
手写约束的基本代码是这样的:
self.animationConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:VisualFormal
options:0
metrics:nil
views:dic];
[self.view addConstraints:self.animationConstraints];
其中options
、metrics
基本默认,到底什么干嘛用的请自行谷歌。
views
后面跟一个数组,用来给约束的对象“取外号”,比如:
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:self.view1,@"view0",
self.view2,@"view1",
self.view3,@"view2",
self.view4,@"view3",
self.view5,@"view4",nil];
就是分别为view1
,view2
,view3
,view4
,view5
这五个View取了view0,view1,view2,view3,view4 的别名。后面需要用到。
然后VisualFormal
是一个新玩意,它是一个NSString
,差不多长这样:
V:[view0(67)]-0-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-|
看着好奇怪是不是,是这样的: *每一个 []
中表示一个视图或一个控件;V 表示垂直约束,H 表示水平约束,比如 V:[view0(67)] 表示垂直方向上view0高67,H:[view0(67)]表示水平方向上view0宽67。然后两个视图之间的 -0- 的意思就很简单了,意思就是这两个视图之间间隔为0,比如 V:[view0(67)]-0-[view1(67)] 就表示竖直方向上 高为67的view1 紧贴在 高为67的view0的下面,因为间隔为0嘛!最后有个 "|",则表示父视图的边沿,比如 V:[view4(67)]-0-| 的意思就是 高为67的view4紧贴父视图的下边沿。 *
Visual Format Language一开始肯定有点搞不清楚,但其要自己不看例子盲写三四个马上能掌握了。
好,那么下面我先举个例子,举完你自己写一个其他的。比如我们怎么用代码约束写一个之前用Storyboard设置的约束呢? 还记得样子吗:
你能看着图写出这个约束吗?请先别往下看,自己硬着头皮想想,想不出来在看答案。
答案:
V:[view0(67)]-0-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-|
当然你可以这直接把这个约束存放到一个NSString里。但不知道你有没有发现,这个约束中是有规律的。基本是 [view0(67)]-0- 循环,所以我们可以这样:
NSString *VisualFormal = @"V:";
for (int i = 0; i < self.imageViewList.count; i++) {
NSString *key = [@"view" stringByAppendingString:[@(i) stringValue]];
NSString *value = [NSString stringWithFormat:@"[%@(67)]-0-", key];
VisualFormal = [VisualFormal stringByAppendingString:value];
}
VisualFormal = [VisualFormal stringByAppendingString:@"|"];
for循环拼出主体 [view0(67)]-0-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-
然后 V:
接上主体,主体再接上 |
就得到想要的约束了。
现在,我们要实现视图放大的效果了。不用怕,我们只要写出 结束帧
的约束就可以了。 先来看一个 结束帧
的布局:
你看到的只有黑色框内的全屏红色矩形,但其实下面其实还有没有放大的视图,被顶出屏幕了所以你看不到。你能写出这个布局的约束吗?老规则,先自己试着写,想不出再看答案。
约束为:(H 代表屏幕高度)
V:|-0-[view1(H)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-[view5(67)]
类似的还有四种情况:
V:|-(-67)-[view1(67)]-0-[view2(H)]-0-[view3(67)]-0-[view4(67)]-0-[view5(67)]
V:|-(-67*2)-[view1(67)]-0-[view2(67)]-0-[view3(H)]-0-[view4(67)]-0-[view5(67)]
V:|-(-67*3)-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(H)]-0-[view5(67)]
V:|-(-67*4)-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-[view5(H)]
从这五个约束中我们可以总结:
1.view1的约束为: -(-67*N)-[view1(67)]
2.点击视图的约束: -0-[viewN(H)]
所以最后 结束帧
是这样:
[self.view removeConstraints:_animationConstraints];
NSString *VisualFormal = @"V:|";
for (int i = 0; i < self.imageViewList.count; ++i) {
NSString *key = [@"view" stringByAppendingString:[@(i) stringValue]];
NSString *value = [NSString stringWithFormat:@"-0-[%@(67)]", key];
if (i == 0) {
value = [NSString stringWithFormat:@"-(-%ld)-[%@(67)]", 67 * _index,key];
}
if (i == _index) {
value = [NSString stringWithFormat:@"-0-[%@(%f)]", key, MAXHEIGHT];
}
VisualFormal = [VisualFormal stringByAppendingString:value];
}
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:self.view1,@"view0",
self.view2,@"view1",
self.view3,@"view2",
self.view4,@"view3",
self.view5,@"view4",nil];
NSLog(@"---------%@",VisualFormal);
self.animationConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:VisualFormal
options:0
metrics:nil
views:dic];
[self.view addConstraints:self.animationConstraints];
到这里,我们已经完成了 结束帧
,那么要让它自动补间,我们还需要 UIView Animation
,这里,千万别忘了
[UIView animateWithDuration:.3f
animations:^{
//上面的结束帧代码...
[self.view layoutIfNeeded];
}];
好了,最后你可以到这里下载完整版源代码。