iOS系統(tǒng)導(dǎo)航欄自定義標(biāo)題動(dòng)畫跳變解析

如果我們使用iOS系統(tǒng)的導(dǎo)航欄,自己設(shè)置titleView,leftItem和rightItem,當(dāng)titleView長(zhǎng)度達(dá)到一定時(shí),push會(huì)出現(xiàn)titleView左右跳變的情況,本文將分析跳變?cè)蚣敖鉀Q辦法。

導(dǎo)航欄的內(nèi)部布局

在一個(gè)全新的APP,自定義導(dǎo)航欄的左中右后,查看布局,會(huì)發(fā)現(xiàn),導(dǎo)航欄內(nèi)部布局如下

在這里插入圖片描述

設(shè)置了自定義leftItem,titleView和rightItem,在導(dǎo)航欄中,我們自定義的view都會(huì)被_UITAMICAdaptorView包裹,其中l(wèi)eftItem和rightItem在_UITAMICAdaptorView外還會(huì)包裹一層_UIButtonBarStackView,最后布局在_UINavigationBarContentView中。

在導(dǎo)航欄內(nèi)部布局的左邊塊、中間塊和右邊塊,以下簡(jiǎn)稱ABC,整個(gè)屏幕寬為Width。

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

安全區(qū)域

A不論寬度如何(包括為0),一定會(huì)距離左邊gap1。

C不論寬度如何(包括為0),一定會(huì)距離右邊gap1。

B就算再寬,也一定會(huì)距離A和C各gap2。

在這里插入圖片描述

(A設(shè)置寬40,B設(shè)置寬414,C設(shè)置寬40)

當(dāng)A和C寬度設(shè)為0時(shí),B距離屏幕左右各(gap1+gap2)。

在這里插入圖片描述

當(dāng)A和C設(shè)置為nil時(shí),B距離屏幕左右各12(gap3)。

在這里插入圖片描述

對(duì)齊方式

當(dāng)增加A的寬度時(shí),A是以左邊不動(dòng),右邊增加來(lái)加寬的,B的寬度會(huì)因A寬度增加而壓縮,A最寬不超過C.left-gap2*2。

在這里插入圖片描述

當(dāng)增加C的寬度時(shí),C是以右邊不動(dòng),左邊增加來(lái)加寬的,B的寬度會(huì)因C寬度增加而壓縮,C最寬不超過A.right-gap2*2。

在這里插入圖片描述

當(dāng)調(diào)節(jié)B的寬度時(shí),B默認(rèn)是以導(dǎo)航欄中心為錨點(diǎn),左右同時(shí)增加,且最大不會(huì)超過 162(Width-A.width-B.width-gap12-gap22)

在這里插入圖片描述

當(dāng)把ABC全部調(diào)成屏幕寬時(shí),B會(huì)被完全擠沒,AC平分除了安全區(qū)域的所有空間(Width-gap12-gap22)

在這里插入圖片描述

導(dǎo)航欄標(biāo)題欄動(dòng)畫

從左到右的跳變的產(chǎn)生

首先理解了前面的布局,可知道B的x坐標(biāo)的相對(duì)于A的計(jì)算公式

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

B的x坐標(biāo)理想情況下是(Width - B.width)/2,也就是動(dòng)畫結(jié)束位置,實(shí)際x坐標(biāo)位置可能是(Width - B.width)/2或者(A.right+gap2)(兩者取最大值),也就是最后布局位置。

當(dāng)實(shí)際位置為A.right+gap2時(shí),說明動(dòng)畫初始位置在實(shí)際位置左邊,就會(huì)出現(xiàn)push時(shí),導(dǎo)航欄title左側(cè)有個(gè)從左到右的跳變。

在這里插入圖片描述

從右到左的跳變的產(chǎn)生

同理,B的right坐標(biāo)的相對(duì)于C的計(jì)算公式

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

B的right坐標(biāo)理想情況下是 (Width + B.width)/2,也就是動(dòng)畫結(jié)束位置,實(shí)際位置可能是(Width + B.width)/2或者(C.left-gap2)(兩者取最小值),也就是最后布局位置。

當(dāng)實(shí)際位置為(C.left-gap2)時(shí),說明動(dòng)畫初始位置在實(shí)際位置右邊,就會(huì)出現(xiàn)push時(shí),導(dǎo)航欄title右側(cè)有個(gè)從右到左的跳變。

在這里插入圖片描述

防止跳變的結(jié)論

為了防止上述兩種跳變,只要令B的left實(shí)際位置為 (Width - B.width)/2,B的right實(shí)際位置為 (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的寬度不能超過屏幕寬減去固定的安全區(qū)域再減去A和C之中最寬的2倍。

解決了?

不,還沒完,到目前這步,是手Q8.0.0之前的做法,設(shè)定了A和C可能存在的最大寬度(因?yàn)锳C的寬度是可能會(huì)變的,比如左邊沒有未讀消息和有99條未讀寬度是不一樣的,再比如右邊可能有一個(gè)圖標(biāo)或兩個(gè)圖標(biāo)),然后得到的B的寬度就很窄了。

如圖,B和A之間還有一大段距離沒有利用上,如果想利用上這段空間,又不希望出現(xiàn)跳變,該怎么辦呢?

推翻從右到左的跳變

首先要再回到導(dǎo)航欄標(biāo)題欄動(dòng)畫 - 從右到左的跳變的產(chǎn)生,其實(shí)因?yàn)橄到y(tǒng)動(dòng)畫本身就是從右到左,所以看不出來(lái)有跳變,會(huì)令人以為是正常的動(dòng)畫,以下兩張圖,就動(dòng)畫而言,不會(huì)令人有跳變的感覺。

在這里插入圖片描述

在這里插入圖片描述

會(huì)有跳變的感覺是因?yàn)榧由蟽?nèi)容后,B的內(nèi)容從C中滑過

在這里插入圖片描述

在這里插入圖片描述

但一般情況下,C放置的都是圖標(biāo),空白區(qū)域很大,B的內(nèi)容從C有動(dòng)畫滑過其實(shí)可以接受。

如果可以接受,那么B的寬度就變?yōu)榱酥灰蕾嘇的寬度

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

不接受“推翻從右到左的跳變”

不行,追求完美的人說,我就是這么一點(diǎn)點(diǎn)跳變都不能接受,而且,上面的方法只解決了C大于A的情況,A大于C的情況還是有問題呀!

好,下面重點(diǎn)介紹下planB——

內(nèi)容越界方案

首先,ABC里的內(nèi)容,是可以超過ABC的寬度限制顯示的?。ê竺鍭BC的內(nèi)容各稱為abc)

什么意思呢,回到上一張圖,當(dāng)我把A的內(nèi)容“< left”的x坐標(biāo)設(shè)為-20,a就頂著屏幕左邊出現(xiàn)了。

如果我把ABC寬度都調(diào)為0,再看內(nèi)容的顯示:


在這里插入圖片描述

可以看到除了a的x坐標(biāo)被我設(shè)了-20,b和c都是以B和C的x坐標(biāo)為原點(diǎn)顯示的,并且是全部顯示,不會(huì)因?yàn)閷挾葹?就不顯示,也就是結(jié)論:ABC內(nèi)容的顯示不會(huì)被其寬度影響,但是會(huì)位置會(huì)受ABC的x坐標(biāo)的影響。(當(dāng)然前提你自己不能給自定義的view設(shè)置clipsToBounds為真)

也就是說,在"防止跳變的結(jié)論"基礎(chǔ)上,我們可以把b的位置根據(jù)AC寬度進(jìn)行調(diào)整,如下圖

在這里插入圖片描述

C比A寬,B和A之間空余了X的寬度(X.width = C.width - A.width),那么b的x起始點(diǎn)位置就可以計(jì)算為 -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坐標(biāo)為0,b的寬度為Width - A.width - C.width - gap12 - gap22。

在這里插入圖片描述

綜上,計(jì)算b的公式為

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

當(dāng)B的背景顏色置為透明時(shí),看效果就只看到B的內(nèi)容了(以下兩圖區(qū)別在于右圖B背景設(shè)為透明)

在這里插入圖片描述

在這里插入圖片描述

(PS.由實(shí)踐看出,當(dāng)a的x坐標(biāo)處于安全區(qū)域gap1內(nèi)時(shí),push動(dòng)畫會(huì)有一個(gè)該區(qū)域從無(wú)到有的變化,同理當(dāng)c的right位置處于最右邊的安全區(qū)域也有,所以建議A和C的內(nèi)容不要越過安全區(qū)域,但是這個(gè)也是有解決辦法的,以后再說。)

基于以上方案,也可以一開始就把B的寬度設(shè)為0,然后每次只需要計(jì)算b的坐標(biāo)和寬度就行了,還可以通過計(jì)算令B把左右gap2的區(qū)域也占掉。

在手Q上的實(shí)踐效果:左圖長(zhǎng)標(biāo)題,右圖短標(biāo)題(左邊的未讀消息數(shù)從無(wú)到有)

在這里插入圖片描述

在這里插入圖片描述

附:不同機(jī)型下gap1和gap2的值

新增gap3(當(dāng)A和C設(shè)為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
如果有幫助到你,請(qǐng)給我Github上一個(gè)Star鼓勵(lì)一下O(∩_∩)O謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容