问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
你好,欢迎来到懂视!登录注册
当前位置: 首页 - 正文

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

发布网友 发布时间:2022-05-12 07:53

我来回答

1个回答

热心网友 时间:2024-03-13 02:20

一、预期效果

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

如上图所示,整个界面都使用了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是否以弹出:

[objc] view plain copy

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

[objc] view plain copy

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


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

constant属性为给定的值:

[objc] view plain copy

- (void)replacePickerContainerViewTopConstraintWithConstant:(CGFloat)constant  

{  

for (NSLayoutConstraint *constraint in self.pickerContainerView.superview.constraints) {  

if (constraint.firstItem == self.pickerContainerView && constraint.firstAttribute == NSLayoutAttributeTop) {  

constraint.constant = constant;  

}  

}  

}  

[objc] view plain copy

- (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约束:

[objc] view plain copy

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

[objc] view plain copy

<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

中设置的值:

[objc] view plain copy

<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>  

[objc] view plain copy

<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的意义。

至此程序达到了预期的效果,下面的gif图展示了动画效果。

1.竖屏:


2.横屏:

三、小结

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

车,虽然频繁换挡(修改frame)很繁琐,却也很享受那种可以完全控制汽车档位的

自由感。使用了autolayout之后则一下子升级为了自动挡汽车,切换档位的活不再由

我们直接操作,而只能通过油门(constraints)的大小来间接的改变汽车的档位。在

自动挡汽车里,我们必须要放弃直接控制档位的想法,那是不可能的了,我们必须要

学会通过熟练掌握脚下的油门和刹车来控制车速!在习惯了自动挡之后,相信大家也

一样能够得心应手的做自己想做的事情。

声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。
E-MAIL:11247931@qq.com
猪肚炖汤的做法 我家的创维电视机最近无论连接DVD影碟机或者是连接有线电视网,电视机都... 小小新娘花的歌词是什么? 胶州小吃有什么 红橡华园的介绍 长沙市芙蓉区有哪些湾 长沙有哪些湖景小区 世界第一大洋--太平洋 太平洋有哪些方面占据世界第一 午夜惊魂2游戏安卓游戏官方下载 成都兢兢餐饮管理有限公司怎么样? 人民币的印制工厂有几个?分别在哪? 印钱厂在哪里?印的钱都到哪里去了? 从宏观经济学货币*的角度出发,大量印制货币会有什么效果?是大量! 我们发行的人民币都是从哪印来的,很多新版,为什么不多印点,这样国家就不会穷了 货币是怎么流通的呢?怎么从印币厂流入到社会呢? 国民政府财政部印刷局旧址的历史沿革 人民币造币厂有招普工么 作文题目【语文,说爱你不容易】或者【语文,心中的一泓清泉】 萧敬腾刺猬头旧照,你的18岁在做什么? 有首古风歌曲的歌词:当垆酒肆声名在。请问:歌名是什么? 命中注定我爱你18的剧情是什么?可以说得详细点吗! 梦洁天猫旗舰店里的DreamCoco, MEE, MINI MEE, 以及Maison品质怎么样? Maison品牌的床品有没有中文名字? Maison品牌的床上用品质量咋样?用过的朋友介绍下 带湘字的宠物名 Maison品牌?主要经营什么的? maison·w哪里有专柜 邯郸市哪有卖TCL KY-25&#47;VY钛金移动空调1P空调一机多用正品联保 西安哪里有TCL KY-25/VY 移动空调? mikey mouse是什么意思 Mikey mouse ,No parblem 这句谚语的意思是什么? 米老鼠是我最喜欢的动画人物 翻译 请帮忙用英语回答一下谢谢 “米老鼠第一次是电影的主角 ” 英文翻译 我决定将我最喜的最喜欢的玩具们给你英语 Mikey是什么单词? 求高手用英语翻译[会追加]21:40之前要 英语题,, 米奇用英文怎么翻译 What does his favourite TV shows怎么回答? 以前中国女排有个叫什么静的来着。身高好像是1米九一的那位。不知现在怎么样了?!都没她的消息了啊! 用英语翻译下列句子: 张静初获过哪些奖 我中文名王飞,帮忙取个英文名字,谢谢啦! 一篇英语的雨伞推销演讲稿。 l历史上叫张静的名人有谁? 电脑文字不清晰怎么设置 上面的文字不清晰,什么办法能把它处理来看清楚? 研究对象与研究区选择
  • 焦点

最新推荐

猜你喜欢

热门推荐