如何对使用了autolayout的UIView添加动画

当我们对一个UIView使用了autolayout自动布局之后,也就意味着我们放弃了传统的

通过设置view的frame等方式手动的修改、确定这个view的位置、尺寸属性。甚至从某种

程度上讲,我们应该忘记view的frame属性:它的确定不再取决于我(手动的直接修改),

而是通过我们在storyboard或者code中提供的约束条件(constraints),通过一个自动布

局引擎(苹果为autolayout采用的是Cassowary布局引擎,参考文档:点击打开链接),计

算出这个view的frame。因此我们可以认为使用了autolayout的view的frame属性是一个只读

的属性。在代码里认为的改动这个view的frame并不能对这个view的frame产生真正的效果(

事实也确实如此)。

现在问题就来了,在以前我们经常通过对一个view的frame的修改产生view移动的动画效

果,那么在使用了autolayout的view世界中我们该如何实现相同的效果呢?答案是,我们“将

计就计”,通过改变这个view上的某个约束constraint然后在uiview的animation block中触发

layout来实现。

一、预期效果

下面我们以一个简单的例子来进行详细的说明:

如上图所示,整个界面都使用了autolayout,现在我们想实现这样一个效果:当

我们点击显示生日的按钮的时候,整个view向上滑动,同时向上推出一个日期选取器

(date picker),类似于点击textfield,弹出键盘后整个界面为了避免被遮住而向上移

动的效果。选取完成日期后点击生日日期按钮或者完成按钮整个view向下缩回,同时

date picker向下滑出可视范围。

二、实现细节

首先来看一眼storyboard中view的层级结构:如下图所示,从图中我们可以看到,

整个view的布局相当简单,就两级:根view和我们的date picker view,其中date

picker view包含了一个完成按钮和系统的date picker。这样的话,要实现整个view和

date picker view同时上移的效果,我们只需要对根view和date picker view同时做动画

即可。

考虑如何实现根view的动画效果,这里我们可以巧妙的通过修改根view的bounds属性

来实现根view的上移效果。注意这里我们需要明白view的bounds属性和frame属性的区别,

前者是相对于当前view的本地坐标系而言的,而后者则是相对于当前view的父view的坐标

系而言的。

简单的讲,frame决定了一个view相对于父view的position和size信息。而bounds则决

定了当前view展示的内容相对于本地坐标系的位置。这里我们将view自身的可视内容和

subviews可以看做一页纸上的内容信息,而view本身可以看成是一枚放于纸上的放大镜,

放大镜的大小不一定是和纸(content size)相同大小的。bounds属性的作用就是确定这枚

放大镜相对于纸的位置:一个bounds =(0, 200, 300, 300)就意味着我们要将这枚放大镜向

纸的下方移动200个points,但放大镜相对于父view的位置仍是保持不变的,这样给我们的效

果就是这个view(显示的内容)向上移动了200个points.

改动bounds的origin属性并不会改动这个view的frame,通过这种展示内容的移动给

我们产生一种view向上移动了的幻觉。如上图中,“哪个位置...”为成为我们放大镜中看到

的第一行。

根view上移动画的效果解决了,下面我们再来看日期选取器date picker,在

storyboard中对其增加的约束如下:定高207、trailing/leading/top相对于super

view (根view)的位置。

确定date picker view y轴方向上下移动的约束显然是top约束,点开top约束,

可以看到该约束的详细内容:

一个约束可以描述为:firstItem.attributeA = secondItem.attributeB * multipler +

constant。

结合上图我们可以得出date picker view的top约束为

datePickerView.Top = topLayoutGuide.bottom * 1 + 400

我们可以通过修改这里的constant值来修改这个top约束以达到预期效果,事实上通过

修改而不是删除旧的constraint再添加新的constraint也正是苹果所推荐的,在

NSLayoutConstraint.h头文件中有如下说明:

这样,date picker view的上下移动就可以通过获取并修改其top约束来实现。需要注意

的是在代码中获取datepicker view的top约束实际上是要在其父view的constraints数组中查找,

这是因为每个view的constraints数组中保存的实际上是layout 子view所需的约束的集合。

我们还要定义个辅助BOOL变量,已判断date picker view是否以弹出:

<span style="font-size:18px;">@property (nonatomic, assign) BOOL hasShowPickerView;</span>

接下来定义一个辅助函数,用于查找date picker view的top约束并修改其constant属性为

给定的值:

- (void)replacePickerContainerViewTopConstraintWithConstant:(CGFloat)constant
{
    for (NSLayoutConstraint *constraint in self.pickerContainerView.superview.constraints) {
        if (constraint.firstItem == self.pickerContainerView && constraint.firstAttribute == NSLayoutAttributeTop) {
            constraint.constant = constant;
        }
    }
}

代码里我们在picker container view (即文中的date picker view)的superview的

constraints属性中查找,如果发现firstItem和firstAttribute属性分别是date picker view和

top,则该constraint即为目标约束,然后修改其constant属性。

在view首次被加载的时候我们想确保date picker view 处于整个view的最底部即隐藏的状

态,因而我们在viewcontroller的viewDidLoad方法中调用辅助方法修改一下date picker

view的top约束:

<span style="font-size:18px;">[self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];</span>

在首次点击birthday button的时候动画修改根view的bounds和date picker view的top

constraint,注意上移gap的计算。再次点击birthday button的时候将根view的bounds恢复

到正常值,date picker view的top constraint也恢复到viewDidLoad中设置的值:

</pre><p class="p1"></p><pre name="code" class="objc"><span style="font-size:18px;">- (IBAction)didTapOnBirthdayButton:(id)sender
{
    self.hasShowPickerView = !self.hasShowPickerView;
    if (self.hasShowPickerView) {
        CGRect birthdayButtonFrame = self.birthdayButton.frame;
        birthdayButtonFrame = [self.view convertRect:birthdayButtonFrame fromView:self.birthdayButton.superview];
        CGFloat birthdayButtonYOffset = birthdayButtonFrame.origin.y + birthdayButtonFrame.size.height;
        CGFloat gap = birthdayButtonYOffset - (self.view.frame.size.height - self.pickerContainerView.frame.size.height);
        CGRect bounds = self.view.bounds;
        if (gap > 0) {
            bounds.origin.y = gap;
        } else {
            gap = 0;
        }
        [self replacePickerContainerViewTopConstraintWithConstant:birthdayButtonYOffset];
        [UIView animateWithDuration:0.25 animations:^{
            self.view.bounds = bounds;
            [self.view layoutIfNeeded];
        }];
    } else {
        [self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];
        CGRect bounds = self.view.bounds;
        bounds.origin.y = 0;
        [UIView animateWithDuration:0.25 animations:^{
            self.view.bounds = bounds;
            [self.view layoutIfNeeded];
        }];
    }
}
</span>

上述代码中的[self.view layoutIfNeed]去掉也是没问题的。可能比较费解的是根view.

bounds.origin.y的上移gap的计算以及top constraint的constant值的计算,关键实在真正理

解view的frame和bounds的意义。

至此程序达到了预期的效果。

三、小结

在使用autolayout之前我们写程序控制界面的构成就好比是开一辆手动挡的汽车,虽然

频繁换挡(修改frame)很繁琐,却也很享受那种可以完全控制汽车档位的自由感。使用了

autolayout之后则一下子升级为了自动挡汽车,切换档位的活不再由我们直接操作,而只能

通过油门(constraints)的大小来间接的改变汽车的档位。在自动挡汽车里,我们必须要放

弃直接控制档位的想法,那是不可能的了,我们必须要学会通过熟练掌握脚下的油门和刹车

来控制车速!在习惯了自动挡之后,相信大家也一样能够得心应手的做自己想做的事情。

时间: 2024-11-08 20:42:58

如何对使用了autolayout的UIView添加动画的相关文章

iOS开发给UIView添加动画Animation

self.testView需要添加动画的view 1.翻转动画 [UIView beginAnimations:@"doflip" context:nil]; [UIView setAnimationDuration:1]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDelegate:self]; [UIView setAnimationTransition:UIViewA

用layer添加UIView的动画

项目有时会遇到用UIView 添加动画的情况,这里我觉得在layer上添加动画比较好,因为可以详细地设定动画属性,方便理解 下面是一个旋转动画: -(void)roundBtnAction:(id)sender {            UIButton * button = (UIButton *)sender;        //Animation自旋转(layer动画):    CABasicAnimation* rotationAnimation;        rotationAnim

细数AutoLayout以来UIView和UIViewController新增的相关API&lt;转写&gt;

细数AutoLayout以来UIView和UIViewController新增的相关API – UIViewController篇 UILayoutSupport @property(nonatomic,readonly,retain) id<UILayoutSupport> topLayoutGuide NS_AVAILABLE_IOS(7_0); @property(nonatomic,readonly,retain) id<UILayoutSupport> bottomLay

Swift中给UIView添加Badge

extension UIView{ //任意UIView添加badge func showBadgeValue(#strBadgeValue: String) -> Void{ let tabBar = UITabBar(frame: CGRectMake(0, 0, 320, 50)) let item = UITabBarItem(title: "", image: nil, tag: 0) item.badgeValue = strBadgeValue let array

为UIView视图切换添加动画效果

我们定义了一个动画类来实现视图切换的动画效果,这个类只包含一个类方法,可直接调用,具体代码如下: 头文件: + ? 1 2 3 4 5 6 7 8 9 10 11 12 13 #import <Foundation/Foundation.h> @interface ViewAnimation : NSObject /*============================页面切换的方法==============================     View1 表示当前页面     Vi

给UIView添加手势

对于不能addTarget的UI对象,添加手势为他们带来了“福音”,以为UIView添加手势为例,揭开手势的面目. 1,创建一个view先, UIView * jrView=[[UIViewalloc] initWithFrame:CGRectMake(0, 0, 160, 160)]; jrView.center=self.view.center; jrView.backgroundColor=[UIColorgreenColor]; [self.viewaddSubview:jrView];

【Swift 2.1】为 UIView 添加点击事件和点击效果

前言 UIView 不像 UIButton 加了点击事件就会有点击效果,体验要差不少,这里分别通过自定义和扩展来实现类似 UIButton 的效果. 声明 欢迎转载,但请保留文章原始出处:) 博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com 正文 一.为 UIView 添加点击事件 extension UIView { func addOnClickListener(target: AnyObject, action: Sel

细数AutoLayout以来UIView和UIViewController新增的相关API

UILayoutSupport 1 @property(nonatomic,readonly,retain) id topLayoutGuide NS_AVAILABLE_IOS(7_0); 2 @property(nonatomic,readonly,retain) id bottomLayoutGuide NS_AVAILABLE_IOS(7_0); 3 4 @protocol UILayoutSupport 5 @property(nonatomic,readonly) CGFloat l

self.view添加UIView时添加动画

CATransition *animation = [CATransition animation]; animation.delegate = self; animation.duration = 0.5f; animation.timingFunction = UIViewAnimationCurveEaseInOut; if ([subView isKindOfClass:[AreaNumPickerView class]]) { animation.subtype = kCATransi