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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345