国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android透明化和沉浸式狀態(tài)欄實踐及源碼分析

2019-12-12 03:30:44
字體:
來源:轉載
供稿:網友

本文所提到的透明狀態(tài)欄其實指的是將頂部的導航欄延伸到狀態(tài)欄,使之渾然一體(Google官方建議狀態(tài)欄顏色比導航欄的顏色略深一點),并不代表一定不設置背景色,比如導航欄是白色,則可設置狀態(tài)欄為白色,視情況而定。

相比于iOS系統(tǒng),Android系統(tǒng)對于狀態(tài)欄的設置就顯得稍微復雜了一點。Android系統(tǒng)提供了API 19以上對狀態(tài)欄的設置接口,而直到API 23以上才提供對于icon顏色的設置,還有就是各家廠商(如魅族,小米等)對于狀態(tài)欄的有自己的定制,對于需要使用淺色背景狀態(tài)欄的應用,沒處理好的話往往導致淺色背景,白色icon ,狀態(tài)欄不分你我的悲劇。。

(內心os:嗯?右上角那一個綠色的電池,用戶一定知道他是狀態(tài)欄對吧。)

我隨即對比了一些主流app,發(fā)現(xiàn)在我的魅藍2(Android 5.1 Flyme 4.5)上竟然都不支持透明狀態(tài)欄,這對于我這種追求審美的人(其實是視覺提的需求)來說簡直不能忍。在我折騰了幾天之后,終于解決了這些問題,希望對大家思路有一些幫助。

言歸正傳,本文主要針對以下幾點進行分析:1.是否隱藏狀態(tài)欄(全屏模式) 2.狀態(tài)欄的背景色的設置 3.狀態(tài)欄icon的顏色的設置,而對于透明狀態(tài)欄設置過程中,可能造成的icon顏色設置成功,而背景顏色設置失敗,等等原因造成的淺色底,淺色字或深色底,深色字等錯誤情況的處理及兜底方案 4.源碼實現(xiàn)。

下面我們就以上幾點來討論一下Android中透明狀態(tài)欄的實現(xiàn)。

1. 全屏模式(沉浸式狀態(tài)欄)

這種情況其實用得并不多,基本上使用場景在閃屏頁展示廣告或logo,以及一些閱讀類app需要盡可能的利用到屏幕大小,展示更多的內容。

設置方法很簡單,分兩種,在API > 16時:

theme中定義:將自定義該Activtiy的theme,并在其中添加

<item name="android:windowFullscreen">true</item>

代碼中定義:

復制代碼 代碼如下:

window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

2. 狀態(tài)欄的背景色

我們都知道,在Android5.0(即API > 21)時,Google官方提供接口設置對應的狀態(tài)欄背景色

window.setStatusBarColor(@ColorInt int color);

那我們要想在Android5.0以下設置背景色就真的沒有辦法了嗎?并不是,我們發(fā)現(xiàn)在Android4.4以后,出現(xiàn)了windowTranslucentStatus這一特性,所以思路如下:

  • 先設置狀態(tài)欄的透明屬性
  • 給根布局頂部加上一個和狀態(tài)欄一樣大小的矩形View,充當假的狀態(tài)欄
  • 設置FitsSystemWindows屬性為true,此時跟布局會延伸到狀態(tài)欄,處在頂部位置就是之前設置的view,這樣就可以以假亂真了。 
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {   // 設置狀態(tài)欄透明   activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);   // 生成一個狀態(tài)欄大小的矩形View   View statusView = createStatusView(activity, color);   ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();   decorView.addView(statusView);   // 設置根布局的參數(shù)   ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);   rootView.setFitsSystemWindows(true);   rootView.setClipToPadding(true);  }

設置矩形色塊的代碼如下:

 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);  // 繪制一個和狀態(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的顏色

我們通過調研發(fā)現(xiàn),魅族和小米的兩個廠商分別對于Flyme及MIUI做了狀態(tài)欄的定制,所以我們可以在Flyme4.0以上及MIUIV6以上都可以實現(xiàn)icon顏色的替換,結合上述的狀態(tài)欄背景色的替換,我們就可以對于魅族及小米兩款機型適配淺色的導航欄了。

3.1 魅族Flyme4.0+

 /**  * 魅族Flyme4+以上透明狀態(tài)欄  * @param window   * @param dark 是否把狀態(tài)欄字體及圖標顏色設置為深色  * @return 是否設置成功  */ 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) {    //錯誤處理   }  }  return result; }

3.2 MIUIV6.0+

 /**  * 設置狀態(tài)欄字體圖標為深色,需要MIUIV6以上  * @param window  * @param dark 是否把狀態(tài)欄字體及圖標顏色設置為深色  * @return 是否設置成功  *  */ 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){    //錯誤處理   }  }  return result; }

效果如下:

4. 設置背景色失敗處理方案

4.1 解決思路

說了那么多,有童鞋可能就問了,上述說的代碼可能對大多數(shù)機型有效,但是萬一某些機型設置失敗了呢?體驗不但沒提升,反倒讓用戶不能忍受了。

好吧,下面我們來看一下應該怎么解決。思路如下:

  • 獲取狀態(tài)欄的背景色,與需要設置的顏色進行比對,如果相等則設置成功。
  • 如果背景色設置失敗,針對非Flyme機型,我們回退狀態(tài)欄icon顏色,還原到最初狀態(tài)。
  • 上述為什么要強調非Flyme機型,是因為用過Flyme機型的童鞋肯定知道有一個透明狀態(tài)欄的開關,當開啟后,F(xiàn)lyme會對狀態(tài)欄進行處理,但處理效果并不和我們想要的一樣(比如視圖頂部為黑色背景,但是卻設置了白色狀態(tài)欄),并且兩者都對狀態(tài)欄進行操作,操作的沖突也有可能引起上述的icon顏色不明顯的情況,所以我們自己定義一個ColorDrawable進行替換。

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; }

下面講一下針對如何屏蔽Flyme機型沉浸式狀態(tài)欄打開時的影響:

 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 兜底方案

上面提到了一些失敗判斷途徑,那么如果還是對一些機型無效呢?我的建議是可以在本地預埋一個開關,萬一出問題,服務器可以對相應機型的沉浸式開關關掉即可,或者采用hotfix等方式來進行修復。

5. 源碼分析

看過上面針對魅族和小米設置icon的源代碼我們知道,共同點都是通過反射來進行調用相應的方法,或者對于相應的狀態(tài)欄視圖進行設置顏色。我們來看看源碼,在Window中有一個抽象方法是setStatusBarColor而在PhoneWindow中我們找到了該方法的實現(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這兩個都是ColorViewState定義好的ColorViewState,主要存儲了關于statusbar的一些視圖信息。包括id,translucentFlag,verticalGravity等等。

接著看updateColorViewInt()方法,這個方法是對一個View設置顏色的,自然這個View也就是指的狀態(tài)欄的View,下面來看一下實現(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;    //對傳入的ColorViewState的view做非空判斷   if (view == null) {    if (show) {     //新建一個view     state.view = view = new View(mContext);     //設置背景色     view.setBackgroundColor(color);     view.setTransitionName(state.transitionName);     view.setId(state.id);     visibilityChanged = true;     //有人可能會感覺奇怪,為什么show為true的時候,這里反倒要設置INVISIBLE。從后面代碼可以知道,這里無非是想通過動畫來做一個漸顯操作,而不是立馬顯示出來。     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);     }     //設置背景色     view.setBackgroundColor(color);    }   }   //改變狀態(tài)欄visibility屬性時加入動畫   if (visibilityChanged) {    view.animate().cancel();    if (animate) {     if (show) {      //顯示時先設置為全透明      if (view.getVisibility() != VISIBLE) {       view.setVisibility(VISIBLE);       view.setAlpha(0.0f);      }      //關于透明度的動畫插值器      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. 總結

話說了這么多,其實就是想讓大家知道為什么很多app放棄在較低版本的Android系統(tǒng)對狀態(tài)欄進行視覺修改,尤其是淺色導航欄的應用。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 金阳县| 射洪县| 赣榆县| 墨脱县| 同仁县| 莱芜市| 呼和浩特市| 十堰市| 嘉义县| 临洮县| 黎城县| 玉门市| 扎鲁特旗| 垦利县| 兖州市| 太保市| 邵东县| 白水县| 赤水市| 宜黄县| 建平县| 环江| 扶沟县| 共和县| 和硕县| 北宁市| 长春市| 西城区| 乌兰县| 栾城县| 稷山县| 辽阳县| 屏东市| 黄梅县| 苏州市| 陇西县| 巧家县| 香格里拉县| 庆安县| 金寨县| 金寨县|