這個(gè)項(xiàng)目提供了非常多精心挑選的Android庫,、工具,、開源項(xiàng)目、書籍,、博客,、教程等,質(zhì)量非常高,,如果你不清楚自己要學(xué)習(xí)什么,,或者想要挑選一些開源庫學(xué)習(xí)源碼,這個(gè)項(xiàng)目應(yīng)該可以幫助你,。
https://github.com/aritraroy/UltimateAndroidReference
本文由kyleduo投稿,。 kyleduo的博客地址: https://blog.
Material Design將Immersive(沉浸感)這個(gè)詞帶到了我們面前。相比于全屏沉浸感,,我們見到更多的,,是在4.4及以上版本的半透明狀態(tài)欄(translucent statusBar)效果。
如下:
注意: 半透明狀態(tài)欄和狀態(tài)欄著色是兩個(gè)不同的概念,。半透明狀態(tài)欄并不是手動(dòng)設(shè)置狀態(tài)欄的顏色,,而是通過設(shè)置Theme的屬性,讓內(nèi)容(content)顯示在狀態(tài)欄下面,。
網(wǎng)上提到這個(gè)效果的不少,,但是真的講明白的不多。下面我來帶你一起digging~
上面示例圖是5.0及以上系統(tǒng)(API v21)的效果,,因?yàn)锳ndroid版本的關(guān)系,,在4.4(API v19)上,相同的屬性會(huì)導(dǎo)致不同的效果,。
下面是對(duì)比:
可以看到,,4.4上的效果相對(duì)于5.0及以上(下面說的5.0都指5.0及以上)的半透明,4.4上是一個(gè)漸變的效果,。
不去討論孰優(yōu)孰劣,,暫時(shí)記住這個(gè)效果。
上面就是Demo的效果,,Demo只有一個(gè)頁面,,層級(jí)如下:
但是4.4和5.0還有更大的不同,下面提到的實(shí)現(xiàn)方式的不同將導(dǎo)致編碼的差異,。
半透明狀態(tài)欄是在4.4版本引入的,,但是fitsSystemWindows這個(gè)屬性在API 16就已經(jīng)引入了,用來讓View根據(jù)Window的縮進(jìn)(WindowInsets)進(jìn)行響應(yīng)處理——設(shè)置padding或者offset,;不只是用在狀態(tài)欄上,,NavigationBar對(duì)Content的影響,,也通過這個(gè)標(biāo)記位傳遞到View上。
回顧一下通常我們看到的實(shí)現(xiàn)方法:
在styles.xml中設(shè)置android:windowTranslucentStatus屬性為true,。 在布局文件中設(shè)置android:fitsSystemWindows屬性為true,。
如果你真的查到了這些方法并且按照上面寫的做了,那么極大可能你做出的效果在4.4和5.0上是不一樣的,,或者是需要再次調(diào)整和嘗試修改,。
上面提到的步驟中,第一步是沒有問題的,,4.4及以上的styles.xml文件加上這個(gè)屬性即可,。
關(guān)鍵在于第二步,fitsSystemWindows屬性應(yīng)該設(shè)置到哪個(gè)/些View上,?
這個(gè)問題引出了這篇博客的關(guān)鍵,,4.4和5.0系統(tǒng)對(duì)于fitsSystemWindows屬性解析的不同實(shí)現(xiàn)。我也是遇到了設(shè)置這個(gè)屬性的問題,,才開始進(jìn)行實(shí)現(xiàn)原理的分析,,發(fā)現(xiàn)之前的理解和使用,根本就是錯(cuò)的,。
4.4 KitKat v19
兩個(gè)系統(tǒng)的處理邏輯的相同點(diǎn)是,,都是從ViewTree的頂端開始,向下進(jìn)行深度優(yōu)先遍歷,,讓各級(jí)子View進(jìn)行處理,。不同點(diǎn)在于派發(fā)的方法和邏輯。
要看的方法是View.fitSystemWindows方法和ViewGroup.fitSystemWindows方法(v19 SDK),,方法下面有解釋,。
View.fitSystemWindows
根據(jù)自己的標(biāo)記為判斷是否要響應(yīng)insets。如果需要的話,,調(diào)用internalSetPadding方法設(shè)置padding,。
ViewGroup.fitSystemWindows
深度遍歷子View,依次調(diào)用自己和子View的fitSystemWindows方法,,一旦fitSystemWindows方法返回true,停止遍歷,,完成處理,。
總結(jié)一下就是:深度遍歷,直到給第一個(gè)設(shè)置標(biāo)記的View設(shè)置padding,。
5.0 Lollipop v21
這是說是v21,,其實(shí)源碼里很多地方是按照v20作為分界點(diǎn),但是里面的邏輯并不會(huì)執(zhí)行(因?yàn)橥鈱佑衯21判斷),,所以這里可以按照v21來區(qū)分,。
我們?cè)趘21的SDK中可以看到,,上面的View.fitSystemWindows已經(jīng)過時(shí)了:
This method was deprecated in API level 20. As of API 20 use dispatchApplyWindowInsets(WindowInsets) to apply insets to views. Views should override onApplyWindowInsets(WindowInsets) or usesetOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener) to implement handling their own insets.
依然看一下方法實(shí)現(xiàn):
else分支的fitSystemWindowsInt方法就是v19的實(shí)現(xiàn)。 PFLAG3_APPLYING_INSETS標(biāo)記表明了正在處理dispatchApplyWindowInsets遍歷,。
v21開始使用dispatch/apply邏輯,,類似TouchEvent事件處理。父控件依次調(diào)用dispatchApplyWindowInsets方法,,而View類的dispatchApplyWindowInsets方法中使用onApplyWindowInsets方法或者OnApplyWindowInsetsListener對(duì)象進(jìn)行處理,。
下面看一下主要的三個(gè)方法。
ViewGroup.dispatchApplyWindowInsets
如果自己沒有消費(fèi)并且子View也沒有消費(fèi),,交給父View處理,。 遍歷的結(jié)束條件就是insets對(duì)象的isConsumed標(biāo)記為true,因?yàn)榘裪nsets返回給了父View,,父View的相同方法也會(huì)停止遍歷,,依次向上。
View.dispatchApplyWindowInsets
View.onApplyWindowInsets
consumeSystemWindowInsets方法將消費(fèi)掉所有Inset并將isConsumed()標(biāo)記置為true,。
總結(jié)一下:深度遍歷,,從上至下依次消費(fèi)Insets,直到WindowInsets的isConsumed方法返回true,,通常是調(diào)用過consumeSystemWindowInsets方法,。
下面討論上面例子中出現(xiàn)的ViewGroup的實(shí)現(xiàn)以及不同參數(shù)設(shè)置的情況,為了節(jié)省空間,,方便輸入,,關(guān)鍵的四個(gè)View:AppBarLayout、CollapsingToolbarLayout,、Toolbar,、ImageView(Toolbar同級(jí))分別簡(jiǎn)寫為ABL、CTL,、TB,、IV,然后用T和F表示fitsSystemWindows屬性的值,。
ABL,、CTL等Support包中的View,使用通過ViewCompact設(shè)置OnApplyWindowInsetsListener的方式,,處理,,通常寫法如下:
onWindowInsetChanged是核心方法;但要注意setOnApplyWindowInsetsListener只在5.0及以上SDK生效,,也就是說onWindowInsetChanged方法在5.0以下版本不會(huì)被調(diào)用,。
AppBarLayout.onWindowInsetChanged()
invalidateScrollRanges將重置ScrollRange相關(guān)的標(biāo)記位,同時(shí)記錄insets的值,其他位置通過getTopInset()獲取insets對(duì)頂部偏移量的影響(只有insets的top影響布局),。 ABL不消費(fèi)Insets,。
CollapsingToolbarLayout.onWindowInsetChanged()
可以看到,只要CTL的標(biāo)記為true,,是一定消費(fèi)Insets的,。 mLastInsets屬性在onLayout方法中用到,下面是相關(guān)實(shí)現(xiàn):
這個(gè)邏輯是: 遍歷所有子View,,如果子View沒有設(shè)置fitsSystemWindows標(biāo)記,,只要getTop()的值小于insetTop,就將其偏移到insetTop,。 換句話說:設(shè)置了標(biāo)記的子View會(huì)在StatusBar下面(under)繪制,,沒有設(shè)置標(biāo)記的子View會(huì)被擠下去(down),。
同時(shí),,CTL還有一個(gè)有意思的邏輯:
如果CTL的直接父View是ABL,,會(huì)同步ABL的fitsSystemWindows屬性的值,。
下面開始討論各種不同參數(shù)的情況。
均為true: ABL = CTL = TB = IV = T
結(jié)果:
錯(cuò)誤原因: 4.4:深度遍歷,,第一個(gè)遇到的View是ABL,,執(zhí)行View的默認(rèn)邏輯,,設(shè)置paddingTop,。所以露出了背景顏色,,同時(shí)子View都被擠到了下面。 5.0:因?yàn)镃TL設(shè)置了true,,而且子View也都設(shè)置了true,,所以TB和IV都在StatusBar下面繪制。
先保證5.0能顯示,,根據(jù)上面的錯(cuò)誤,,我們應(yīng)該把TB設(shè)置為false,IV設(shè)置為true,。這樣5.0會(huì)顯示正常,,而4.4,情況和上面一樣,。
結(jié)果:
符合預(yù)期(4.4沒有換圖,,一模一樣,偷懶了),,5.0正常,,原因其實(shí)上面也說了,這里不再重復(fù),。只要清楚了原理,結(jié)果就顯而易見了。 如果到這里你不太理解,,再看看上面源碼,。
下面輪到4.4了。
回頭看一下ViewTree的層級(jí)關(guān)系:ABL -> CTL -> IV,、TB,。從上到下,我們期望的是TB執(zhí)行View默認(rèn)的邏輯(設(shè)置padding),,所以應(yīng)該是F -> F -> F,、T。(IV應(yīng)該是false,,因?yàn)橐霈F(xiàn)在StatusBar下面),。
結(jié)果:
符合預(yù)期,4.4顯示正確,,但是5.0回到了第一次嘗試的結(jié)果,。原因就留給你啦,相信你一定能解釋,。
經(jīng)過幾次嘗試,,我們發(fā)現(xiàn)要使得4.4和5.0都正確顯示,需要給View的屬性根據(jù)SDK版本設(shè)置不同的值,,我們可以通過styles.xml簡(jiǎn)化這個(gè)操作,。
v19
v21
v21的CTL可以設(shè)置為false,是因?yàn)镃TL的值會(huì)同步ABL的值,,所以這里的值沒有作用,,T或者F的結(jié)果是一樣的。
layout長這樣(省略了無關(guān)屬性)
以上,,我們可以實(shí)現(xiàn)適配4.4和5.0的半透明狀態(tài)欄效果,,通知知曉了其中的原理。下面再討論兩個(gè)場(chǎng)景:
普通布局
上面的實(shí)例中使用到的布局都是Support包中的,,還有一種情況是我們使用的是普通的布局,,比如Linearlayout、RelativeLayout等,。使用這些布局,,應(yīng)該怎么實(shí)現(xiàn)呢?
普通布局都使用默認(rèn)實(shí)現(xiàn),,不管是4.4和5.0都將進(jìn)行深度優(yōu)先遍歷,,直到WindowInsets被消費(fèi)。所以對(duì)于普通布局的做法是,,只給ToolBar設(shè)置fitsSystemWindows=true屬性,。 不僅僅是ToolBar,任何布局都直接給期望的那個(gè)View設(shè)置fitsSystemWindows=true屬性即可。
4.4 效果優(yōu)化
4.4上的漸變效果,,會(huì)顯得ToolBar很高,,視覺效果并不好。我們可以針對(duì)4.4做一些優(yōu)化,,在上面覆蓋顯示一個(gè)半透明View來模擬5.0上的效果,,以第一個(gè)頁面為例:
act_main.xml
@layout/stub_kitkat_statusbar只包含一個(gè)背景為半透明的View。
MainActivity.java
這里獲取ToolBar的PaddingTop并設(shè)置為Overlay的高度,。
總結(jié)
4.4和5.0處理WindowInsets的邏輯,,相同點(diǎn)是都進(jìn)行深度優(yōu)先遍歷。
不同點(diǎn)是4.4逐級(jí)調(diào)用fitSystemWindows方法,,第一個(gè)帶有fitsSystemWindows屬性的View處理之后,,整個(gè)流程結(jié)束;
5.0通過類似Touch事件的dispatch和apply邏輯完成對(duì)WindowInsets的消費(fèi),,消費(fèi)可以通過onApplayWindowInsets方法或者Listener的方式,,直到消費(fèi)完成,流程結(jié)束,。
fitsSystemWindows屬性表明該View將根據(jù)Window的Insets進(jìn)行適應(yīng),,這個(gè)“適應(yīng)”,一般來說是設(shè)置padding,,CollapsingToolbarLayout的處理方法是對(duì)子View進(jìn)行offset偏移,。
共同點(diǎn)是:表明該View的內(nèi)容或者子View要向下移出狀態(tài)欄的區(qū)域。一般情況下,,只需要給一個(gè)View設(shè)置該屬性,。
為了實(shí)現(xiàn)半透明狀態(tài)欄效果,需要做兩件事: 在主題中設(shè)置android:windowTranslucentStatus值為true,。 給恰當(dāng)?shù)腣iew設(shè)置android:fitsSystemWindows屬性為true,。
以Toolbar為例,如果Toolbar在普通布局中,,直接給Toolbar設(shè)置以上屬性即可,;如果是Demo中那種Material Design嵌套結(jié)構(gòu),就需要根據(jù)4.4和5.0的實(shí)現(xiàn)邏輯進(jìn)行適配,,方法這里就不贅述了,。
Demo源碼: https://github.com/kyleduo/ExamplesFromMyBlog/tree/master/DiggingTranslucentStatusBar 后續(xù)踩坑記錄: https://blog./2017/05/03/digging-translucentstatusbar-2/ https://blog./2017/05/05/digging-translucentstatusbar-3/
無論你是有 Java 基礎(chǔ)希望學(xué) Android 開發(fā)的程序員,還是想進(jìn)一步提升能力的 Android 開發(fā)者,,都可以在這個(gè)Udacity & Google 官方參與制作 的課程項(xiàng)目中找到適合自己的成長路徑,!
*獨(dú)家硅谷技術(shù)課程 *行業(yè)領(lǐng)導(dǎo)者設(shè)計(jì)的實(shí)戰(zhàn)項(xiàng)目 *一對(duì)一學(xué)習(xí)輔導(dǎo) *名企頒發(fā)學(xué)習(xí)認(rèn)證 *畢業(yè)直達(dá)滴滴面試
|