iOS系统导航栏自定义标题动画跳变解析

Posted 阿曌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS系统导航栏自定义标题动画跳变解析相关的知识,希望对你有一定的参考价值。

如果我们使用ios系统的导航栏,自己设置titleView,leftItem和rightItem,当titleView长度达到一定时,push会出现titleView左右跳变的情况,本文将分析跳变原因及解决办法。

目录 - 建议顺序观看

导航栏的内部布局

在一个全新的APP,自定义导航栏的左中右后,查看布局,会发现,导航栏内部布局如下

设置了自定义leftItem,titleView和rightItem,在导航栏中,我们自定义的view都会被_UITAMICAdaptorView包裹,其中leftItem和rightItem在_UITAMICAdaptorView外还会包裹一层_UIButtonBarStackView,最后布局在_UINavigationBarContentView中。

在导航栏内部布局的左边块、中间块和右边块,以下简称ABC,整个屏幕宽为Width。

以下以iPhone XS Max为例,gap1为20,gap2为6。

安全区域

A不论宽度如何(包括为0),一定会距离左边gap1。

C不论宽度如何(包括为0),一定会距离右边gap1。

B就算再宽,也一定会距离A和C各gap2。

(A设置宽40,B设置宽414,C设置宽40)

当A和C宽度设为0时,B距离屏幕左右各(gap1+gap2)。

当A和C设置为nil时,B距离屏幕左右各12(gap3)。

对齐方式

当增加A的宽度时,A是以左边不动,右边增加来加宽的,B的宽度会因A宽度增加而压缩,A最宽不超过C.left-gap2*2。

当增加C的宽度时,C是以右边不动,左边增加来加宽的,B的宽度会因C宽度增加而压缩,C最宽不超过A.right-gap2*2。

当调节B的宽度时,B默认是以导航栏中心为锚点,左右同时增加,且最大不会超过 162(Width-A.width-B.width-gap12-gap22)

当把ABC全部调成屏幕宽时,B会被完全挤没,AC平分除了安全区域的所有空间(Width-gap12-gap22)

导航栏标题栏动画

从左到右的跳变的产生

首先理解了前面的布局,可知道B的x坐标的相对于A的计算公式

B.left = Max( (Width - B.width)/2 , A.right+gap2)

B的x坐标理想情况下是(Width - B.width)/2,也就是动画结束位置,实际x坐标位置可能是(Width - B.width)/2或者(A.right+gap2)(两者取最大值),也就是最后布局位置。

当实际位置为A.right+gap2时,说明动画初始位置在实际位置左边,就会出现push时,导航栏title左侧有个从左到右的跳变。

从右到左的跳变的产生

同理,B的right坐标的相对于C的计算公式

B.right = Min( (Width + B.width)/2 , C.left-gap2)

B的right坐标理想情况下是 (Width + B.width)/2,也就是动画结束位置,实际位置可能是(Width + B.width)/2或者(C.left-gap2)(两者取最小值),也就是最后布局位置。

当实际位置为(C.left-gap2)时,说明动画初始位置在实际位置右边,就会出现push时,导航栏title右侧有个从右到左的跳变。

防止跳变的结论

为了防止上述两种跳变,只要令B的left实际位置为 (Width - B.width)/2,B的right实际位置为 (Width + B.width)/2,也就是

求 (Width - B.width)/2 > (A.right+gap2) 且 (Width + B.width)/2 < C.left-gap2 的 B.width的取值范围?
因已知 A.right = gap1 + A.width + gap2,且 C.left = Width - gap2 - C.width - gap1
可求得B的宽度限制为
B.width < Width - gap12 - gap22 - A.width2 且 B.width < Width - gap12 - gap22 - C.width2
也就是 B.width < Width - gap12 - gap22 - Max(A.width, C.width)*2

翻译成中文就是B的宽度不能超过屏幕宽减去固定的安全区域再减去A和C之中最宽的2倍。

解决了?

不,还没完,到目前这步,是手Q8.0.0之前的做法,设定了A和C可能存在的最大宽度(因为AC的宽度是可能会变的,比如左边没有未读消息和有99条未读宽度是不一样的,再比如右边可能有一个图标或两个图标),然后得到的B的宽度就很窄了。

如图,B和A之间还有一大段距离没有利用上,如果想利用上这段空间,又不希望出现跳变,该怎么办呢?

推翻从右到左的跳变

首先要再回到导航栏标题栏动画 - 从右到左的跳变的产生,其实因为系统动画本身就是从右到左,所以看不出来有跳变,会令人以为是正常的动画,以下两张图,就动画而言,不会令人有跳变的感觉。

会有跳变的感觉是因为加上内容后,B的内容从C中滑过

但一般情况下,C放置的都是图标,空白区域很大,B的内容从C有动画滑过其实可以接受。

如果可以接受,那么B的宽度就变为了只依赖A的宽度

B.width < Width - gap12 - gap22 - A.width*2

不接受“推翻从右到左的跳变”

不行,追求完美的人说,我就是这么一点点跳变都不能接受,而且,上面的方法只解决了C大于A的情况,A大于C的情况还是有问题呀!

好,下面重点介绍下planB——

内容越界方案

首先,ABC里的内容,是可以超过ABC的宽度限制显示的!(后面ABC的内容各称为abc)

什么意思呢,回到上一张图,当我把A的内容“< left”的x坐标设为-20,a就顶着屏幕左边出现了。

如果我把ABC宽度都调为0,再看内容的显示:

可以看到除了a的x坐标被我设了-20,b和c都是以B和C的x坐标为原点显示的,并且是全部显示,不会因为宽度为0就不显示,也就是结论:ABC内容的显示不会被其宽度影响,但是会位置会受ABC的x坐标的影响。(当然前提你自己不能给自定义的view设置clipsToBounds为真)

也就是说,在"防止跳变的结论"基础上,我们可以把b的位置根据AC宽度进行调整,如下图

C比A宽,B和A之间空余了X的宽度(X.width = C.width - A.width),那么b的x起始点位置就可以计算为 -X.width(也就是A.width - C.width),b的最大宽度为Width - A.width - C.width - gap12 - gap22;

同理假如A比C宽,B和C之间就空余了X的宽度(X.width = A.width - C.width),那么b的x坐标为0,b的宽度为Width - A.width - C.width - gap12 - gap22。

综上,计算b的公式为

b.left = Min(0, A.width - C.width)
b.width = Width - A.width - C.width - gap12 - gap22

当B的背景颜色置为透明时,看效果就只看到B的内容了(以下两图区别在于右图B背景设为透明)

(PS.由实践看出,当a的x坐标处于安全区域gap1内时,push动画会有一个该区域从无到有的变化,同理当c的right位置处于最右边的安全区域也有,所以建议A和C的内容不要越过安全区域,但是这个也是有解决办法的,以后再说。)

基于以上方案,也可以一开始就把B的宽度设为0,然后每次只需要计算b的坐标和宽度就行了,还可以通过计算令B把左右gap2的区域也占掉。

在手Q上的实践效果:左图长标题,右图短标题(左边的未读消息数从无到有)

附:不同机型下gap1和gap2的值

新增gap3(当A和C设为nil,B距离屏幕左右距离)

综上,可以判断

if (SCREEN_WIDTH > 375) 
    gap1 = 20;
    gap3 = 12
 else 
    gap1 = 16;
    gap3 = 8;

    gap2 = 6;

Demo源码:https://github.com/Xieyupeng520/AZNavigationBar/tree/master
如果有帮助到你,请给我Github上一个Star鼓励一下O(∩_∩)O谢谢!

以上是关于iOS系统导航栏自定义标题动画跳变解析的主要内容,如果未能解决你的问题,请参考以下文章

如何减少iOS导航栏自定义视图的左右间隙

IOS/Objective-C:导航栏自定义Segue效果对比Show Segue

iOS NavigationBar 导航栏自定义

iOS 11导航栏自定义后退按钮问题

iOS个人中心渐变动画微信对话框标签选择器自定义导航栏短信验证输入框等源码

iPad导航栏自定义高度