摘要:何為透明狀態(tài)欄有人說(shuō)透明狀態(tài)欄就是沉浸式狀態(tài)欄,這里不多解釋?zhuān)斠?jiàn)為什么在國(guó)內(nèi)會(huì)有很多用戶(hù)把透明欄稱(chēng)作沉浸式頂欄。下面我們就以上幾點(diǎn)來(lái)討論一下中透明狀態(tài)欄的實(shí)現(xiàn)。
何為透明狀態(tài)欄?有人說(shuō)透明狀態(tài)欄就是沉浸式狀態(tài)欄,這里不多解釋?zhuān)斠?jiàn)為什么在國(guó)內(nèi)會(huì)有很多用戶(hù)把「透明欄」(Translucent Bars)稱(chēng)作 「沉浸式頂欄」?。本文將對(duì)于透明狀態(tài)欄設(shè)置方法做一個(gè)講解,以及遇到的問(wèn)題該怎么解決,來(lái)面對(duì)各種需求。
本文所提到的透明狀態(tài)欄其實(shí)指的是將頂部的導(dǎo)航欄延伸到狀態(tài)欄,使之渾然一體(Google官方建議狀態(tài)欄顏色比導(dǎo)航欄的顏色略深一點(diǎn)),并不代表一定不設(shè)置背景色,比如導(dǎo)航欄是白色,則可設(shè)置狀態(tài)欄為白色,視情況而定。
相比于iOS系統(tǒng),Android系統(tǒng)對(duì)于狀態(tài)欄的設(shè)置就顯得稍微復(fù)雜了一點(diǎn)。Android系統(tǒng)提供了API 19以上對(duì)狀態(tài)欄的設(shè)置接口,而直到API 23以上才提供對(duì)于icon顏色的設(shè)置,還有就是各家廠商(如魅族,小米等)對(duì)于狀態(tài)欄的有自己的定制,對(duì)于需要使用淺色背景狀態(tài)欄的應(yīng)用,沒(méi)處理好的話(huà)往往導(dǎo)致淺色背景,白色icon ,狀態(tài)欄不分你我的悲劇。。
(內(nèi)心os:嗯?右上角那一個(gè)綠色的電池,用戶(hù)一定知道他是狀態(tài)欄對(duì)吧。)
我隨即對(duì)比了一些主流app,發(fā)現(xiàn)在我的魅藍(lán)2(Android 5.1 Flyme 4.5)上竟然都不支持透明狀態(tài)欄,這對(duì)于我這種追求審美的人(其實(shí)是視覺(jué)提的需求)來(lái)說(shuō)簡(jiǎn)直不能忍。在我折騰了幾天之后,終于解決了這些問(wèn)題,希望對(duì)大家思路有一些幫助。
言歸正傳,本文主要針對(duì)以下幾點(diǎn)進(jìn)行分析:1.是否隱藏狀態(tài)欄(全屏模式) 2.狀態(tài)欄的背景色的設(shè)置 3.狀態(tài)欄icon的顏色的設(shè)置,而對(duì)于透明狀態(tài)欄設(shè)置過(guò)程中,可能造成的icon顏色設(shè)置成功,而背景顏色設(shè)置失敗,等等原因造成的淺色底,淺色字或深色底,深色字等錯(cuò)誤情況的處理及兜底方案 4.源碼實(shí)現(xiàn)。
下面我們就以上幾點(diǎn)來(lái)討論一下Android中透明狀態(tài)欄的實(shí)現(xiàn)。
1. 全屏模式(沉浸式狀態(tài)欄)這種情況其實(shí)用得并不多,基本上使用場(chǎng)景在閃屏頁(yè)展示廣告或logo,以及一些閱讀類(lèi)app需要盡可能的利用到屏幕大小,展示更多的內(nèi)容。
設(shè)置方法很簡(jiǎn)單,分兩種,在API > 16時(shí):
theme中定義:將自定義該Activtiy的theme,并在其中添加
- true
代碼中定義:
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)2. 狀態(tài)欄的背景色
我們都知道,在Android5.0(即API > 21)時(shí),Google官方提供接口設(shè)置對(duì)應(yīng)的狀態(tài)欄背景色
window.setStatusBarColor(@ColorInt int color);
那我們要想在Android5.0以下設(shè)置背景色就真的沒(méi)有辦法了嗎?并不是,我們發(fā)現(xiàn)在Android4.4以后,出現(xiàn)了windowTranslucentStatus這一特性,所以思路如下:
先設(shè)置狀態(tài)欄的透明屬性
給根布局頂部加上一個(gè)和狀態(tài)欄一樣大小的矩形View,充當(dāng)假的狀態(tài)欄
設(shè)置FitsSystemWindows屬性為true,此時(shí)跟布局會(huì)延伸到狀態(tài)欄,處在頂部位置就是之前設(shè)置的view,這樣就可以以假亂真了。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 設(shè)置狀態(tài)欄透明 activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // 生成一個(gè)狀態(tài)欄大小的矩形View View statusView = createStatusView(activity, color); ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); decorView.addView(statusView); // 設(shè)置根布局的參數(shù) ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); rootView.setFitsSystemWindows(true); rootView.setClipToPadding(true); }
設(shè)置矩形色塊的代碼如下:
private static View createStatusView(Activity activity, int color) { // 獲得狀態(tài)欄高度 int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android"); int statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId); // 繪制一個(gè)和狀態(tài)欄一樣高的矩形 View statusView = new View(activity); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight); statusView.setLayoutParams(params); statusView.setBackgroundColor(color); return statusView; }
效果如下:
3. 狀態(tài)欄icon的顏色我們通過(guò)調(diào)研發(fā)現(xiàn),魅族和小米的兩個(gè)廠商分別對(duì)于Flyme及MIUI做了狀態(tài)欄的定制,所以我們可以在Flyme4.0以上及MIUIV6以上都可以實(shí)現(xiàn)icon顏色的替換,結(jié)合上述的狀態(tài)欄背景色的替換,我們就可以對(duì)于魅族及小米兩款機(jī)型適配淺色的導(dǎo)航欄了。
3.1 魅族Flyme4.0+/** * 魅族Flyme4+以上透明狀態(tài)欄 * @param window * @param dark 是否把狀態(tài)欄字體及圖標(biāo)顏色設(shè)置為深色 * @return 是否設(shè)置成功 */ public static boolean setMeizuStatusBarDarkIcon(Window window, boolean dark) { boolean result = false; if (window != null) { try { WindowManager.LayoutParams lp = window.getAttributes(); Field darkFlag = WindowManager.LayoutParams.class .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); Field meizuFlags = WindowManager.LayoutParams.class .getDeclaredField("meizuFlags"); darkFlag.setAccessible(true); meizuFlags.setAccessible(true); int bit = darkFlag.getInt(null); int value = meizuFlags.getInt(lp); if (dark) { value |= bit; } else { value &= ~bit; } meizuFlags.setInt(lp, value); window.setAttributes(lp); result = true; } catch (Exception e) { //錯(cuò)誤處理 } } return result; }3.2 MIUIV6.0+
/** * 設(shè)置狀態(tài)欄字體圖標(biāo)為深色,需要MIUIV6以上 * @param window * @param dark 是否把狀態(tài)欄字體及圖標(biāo)顏色設(shè)置為深色 * @return 是否設(shè)置成功 * */ public static boolean setMIUIStatusBarLightMode(Window window, boolean dark) { boolean result = false; if (window != null) { Class clazz = window.getClass(); try { int darkModeFlag = 0; Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); darkModeFlag = field.getInt(layoutParams); Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); if(dark){ extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//狀態(tài)欄透明且黑色字體 }else{ extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字體 } result=true; }catch (Exception e){ //錯(cuò)誤處理 } } return result; }
效果如下:
4. 設(shè)置背景色失敗處理方案 4.1 解決思路說(shuō)了那么多,有童鞋可能就問(wèn)了,上述說(shuō)的代碼可能對(duì)大多數(shù)機(jī)型有效,但是萬(wàn)一某些機(jī)型設(shè)置失敗了呢?體驗(yàn)不但沒(méi)提升,反倒讓用戶(hù)不能忍受了。
好吧,下面我們來(lái)看一下應(yīng)該怎么解決。思路如下:
獲取狀態(tài)欄的背景色,與需要設(shè)置的顏色進(jìn)行比對(duì),如果相等則設(shè)置成功。
如果背景色設(shè)置失敗,針對(duì)非Flyme機(jī)型,我們回退狀態(tài)欄icon顏色,還原到最初狀態(tài)。
上述為什么要強(qiáng)調(diào)非Flyme機(jī)型,是因?yàn)橛眠^(guò)Flyme機(jī)型的童鞋肯定知道有一個(gè)透明狀態(tài)欄的開(kāi)關(guān),當(dāng)開(kāi)啟后,F(xiàn)lyme會(huì)對(duì)狀態(tài)欄進(jìn)行處理,但處理效果并不和我們想要的一樣(比如視圖頂部為黑色背景,但是卻設(shè)置了白色狀態(tài)欄),并且兩者都對(duì)狀態(tài)欄進(jìn)行操作,操作的沖突也有可能引起上述的icon顏色不明顯的情況,所以我們自己定義一個(gè)ColorDrawable進(jìn)行替換。
4.2 獲取狀態(tài)欄顏色/** * 獲取狀態(tài)欄顏色 * * @param window 需要獲取的activity * @return color 狀態(tài)欄的顏色值 */ public static int getColor(Window window){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return window.getStatusBarColor(); }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ViewGroup decorView = (ViewGroup) window.getDecorView(); int count = decorView.getChildCount(); if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { int color = Color.TRANSPARENT; Drawable background = decorView.getChildAt(count - 1).getBackground(); if (background instanceof ColorDrawable) color = ((ColorDrawable) background).getColor(); return color; } } return -1; }
下面講一下針對(duì)如何屏蔽Flyme機(jī)型沉浸式狀態(tài)欄打開(kāi)時(shí)的影響:
private static boolean setMeizuStatusColor(Window window, @ColorInt int color, int alpha) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { final View view = window.getDecorView().findViewById(android.R.id.statusBarBackground); if (view != null) { if (!(view.getBackground() instanceof MeizuColorDrawable)) { //替換成自定義ColorDrawable view.setBackground(new MeizuColorDrawable()); } if (view.getBackground() instanceof MeizuColorDrawable) { MeizuColorDrawable colorDrawable = (MeizuColorDrawable) view.getBackground(); int finalColor = StatusBarUtil.calculateStatusColor(color, alpha); view.setBackgroundColor(finalColor); colorDrawable.setColor(finalColor); return true; } } } return false; }4.3 兜底方案
上面提到了一些失敗判斷途徑,那么如果還是對(duì)一些機(jī)型無(wú)效呢?我的建議是可以在本地預(yù)埋一個(gè)開(kāi)關(guān),萬(wàn)一出問(wèn)題,服務(wù)器可以對(duì)相應(yīng)機(jī)型的沉浸式開(kāi)關(guān)關(guān)掉即可,或者采用hotfix等方式來(lái)進(jìn)行修復(fù)。
5. 源碼分析看過(guò)上面針對(duì)魅族和小米設(shè)置icon的源代碼我們知道,共同點(diǎn)都是通過(guò)反射來(lái)進(jìn)行調(diào)用相應(yīng)的方法,或者對(duì)于相應(yīng)的狀態(tài)欄視圖進(jìn)行設(shè)置顏色。
我們來(lái)看看源碼,在Window中有一個(gè)抽象方法是setStatusBarColor而在PhoneWindow中我們找到了該方法的實(shí)現(xiàn)
@Override public void setStatusBarColor(int color) { mStatusBarColor = color; mForcedStatusBarColor = true; if (mDecor != null) { mDecor.updateColorViews(null, false /* animate */); } }
mDecor.updateColorViews()
private WindowInsets updateColorViews(WindowInsets insets, boolean animate) { ... updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor, navBarSize, navBarToRightEdge, 0 /* rightInset */, animate && !disallowAnimate); boolean statusBarNeedsRightInset = navBarToRightEdge && mNavigationColorViewState.present; int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0; updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor, mLastTopInset, false /* matchVertical */, statusBarRightInset, animate && !disallowAnimate); ... }
那么mNavigationColorViewState和mStatusColorViewState又是什么呢?
別著急,我們繼續(xù)往下看
private final ColorViewState mStatusColorViewState = new ColorViewState( SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, Gravity.TOP, Gravity.LEFT, STATUS_BAR_BACKGROUND_TRANSITION_NAME, com.android.internal.R.id.statusBarBackground, FLAG_FULLSCREEN); private final ColorViewState mNavigationColorViewState = new ColorViewState( SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, Gravity.BOTTOM, Gravity.RIGHT, NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, com.android.internal.R.id.navigationBarBackground, 0 /* hideWindowFlag */);
mNavigationColorViewState和mStatusColorViewState這兩個(gè)都是ColorViewState定義好的ColorViewState,主要存儲(chǔ)了關(guān)于statusbar的一些視圖信息。包括id,translucentFlag,verticalGravity等等。
接著看updateColorViewInt()方法,這個(gè)方法是對(duì)一個(gè)View設(shè)置顏色的,自然這個(gè)View也就是指的狀態(tài)欄的View,下面來(lái)看一下實(shí)現(xiàn):
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color, int size, boolean verticalBar, int rightMargin, boolean animate) { state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0 && (getAttributes().flags & state.hideWindowFlag) == 0 && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; boolean show = state.present && (color & Color.BLACK) != 0 && (getAttributes().flags & state.translucentFlag) == 0; boolean visibilityChanged = false; View view = state.view; int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity; //對(duì)傳入的ColorViewState的view做非空判斷 if (view == null) { if (show) { //新建一個(gè)view state.view = view = new View(mContext); //設(shè)置背景色 view.setBackgroundColor(color); view.setTransitionName(state.transitionName); view.setId(state.id); visibilityChanged = true; //有人可能會(huì)感覺(jué)奇怪,為什么show為true的時(shí)候,這里反倒要設(shè)置INVISIBLE。從后面代碼可以知道,這里無(wú)非是想通過(guò)動(dòng)畫(huà)來(lái)做一個(gè)漸顯操作,而不是立馬顯示出來(lái)。 view.setVisibility(INVISIBLE); state.targetVisibility = VISIBLE; LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight, resolvedGravity); lp.rightMargin = rightMargin; addView(view, lp); updateColorViewTranslations(); } } else { int vis = show ? VISIBLE : INVISIBLE; visibilityChanged = state.targetVisibility != vis; state.targetVisibility = vis; if (show) { LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (lp.height != resolvedHeight || lp.width != resolvedWidth || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) { lp.height = resolvedHeight; lp.width = resolvedWidth; lp.gravity = resolvedGravity; lp.rightMargin = rightMargin; view.setLayoutParams(lp); } //設(shè)置背景色 view.setBackgroundColor(color); } } //改變狀態(tài)欄visibility屬性時(shí)加入動(dòng)畫(huà) if (visibilityChanged) { view.animate().cancel(); if (animate) { if (show) { //顯示時(shí)先設(shè)置為全透明 if (view.getVisibility() != VISIBLE) { view.setVisibility(VISIBLE); view.setAlpha(0.0f); } //關(guān)于透明度的動(dòng)畫(huà)插值器 view.animate().alpha(1.0f).setInterpolator(mShowInterpolator). setDuration(mBarEnterExitDuration); } else { view.animate().alpha(0.0f).setInterpolator(mHideInterpolator) .setDuration(mBarEnterExitDuration) .withEndAction(new Runnable() { @Override public void run() { state.view.setAlpha(1.0f); state.view.setVisibility(INVISIBLE); } }); } } else { view.setAlpha(1.0f); view.setVisibility(show ? VISIBLE : INVISIBLE); } } }6. 總結(jié)
話(huà)說(shuō)了這么多,其實(shí)就是想讓大家知道為什么很多app放棄在較低版本的Android系統(tǒng)對(duì)狀態(tài)欄進(jìn)行視覺(jué)修改,尤其是淺色導(dǎo)航欄的應(yīng)用。[捂臉]
本人目前在網(wǎng)易搬磚,如果想要挪挪地方,請(qǐng)猛戳此處,選好部門(mén)及崗位以后,歡迎投簡(jiǎn)歷給我([email protected]),我來(lái)幫您內(nèi)推。
最后歡迎拍磚,說(shuō)得不好的地方歡迎交流~
喜歡的話(huà)可以加個(gè)收藏,謝謝~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66757.html
摘要:前言我們先一起來(lái)回顧一下實(shí)現(xiàn)沉浸式狀態(tài)欄的一般套路。從以上版本才是真正的可以設(shè)置沉浸式體驗(yàn),但也僅僅是操作狀態(tài)欄和導(dǎo)航欄的顯示與隱藏。屬性解釋設(shè)置了表明會(huì)負(fù)責(zé)系統(tǒng)的繪制,繪制透明背景的系統(tǒng)狀態(tài)欄和導(dǎo)航欄,然后用和的顏色填充相應(yīng)的區(qū)域。 前言 我們先一起來(lái)回顧一下實(shí)現(xiàn)沉浸式狀態(tài)欄的一般套路。在Android上,關(guān)于對(duì)StatusBar(狀態(tài)欄)的操作,一直都在不斷改善,并且表現(xiàn)越來(lái)越好,...
閱讀 3083·2021-09-28 09:43
閱讀 918·2021-09-08 09:35
閱讀 1453·2019-08-30 15:56
閱讀 1199·2019-08-30 13:00
閱讀 2745·2019-08-29 18:35
閱讀 1840·2019-08-29 14:07
閱讀 3451·2019-08-29 13:13
閱讀 1344·2019-08-29 12:40