Android 狀態欄關於開發的幾件事

最近手頭作了一個新的項目,開發中對狀態欄的要求比較多,也做了一些總結,分享給你們。php

簡答題html

  • 全屏、不保留狀態欄文字(Splash頁面,歡迎頁面)
  • 全屏保留狀態欄文字(頁面上部有Banner圖)
  • 標題欄與狀態欄顏色一致(部分App風格)
  • 不一樣Fragment中對StatusBar的處理不同
  • 設置狀態欄文字的顏色
  • 切換fragment時,toolBar顯示與否、statusbar顯示與否、statusBar顏色、statusBar文字顏色(新增)

思考題android

  • Activity中window是怎麼回事?裏面有什麼View/ViewGroup?
  • setFitsSystemWindows()是什麼鬼?

簡答題,是本篇文章闡述的內容;思考題,是針對所闡述的內容作一些拓展,反應兩個層面:怎麼開發?爲何能實現這樣的功能?
git

演示代碼傳送門

github

簡答題
   windows

需求1、全屏,不保留狀態欄文字(Splash頁面,歡迎頁面) bash

這個效果你們腦補下,就不貼圖了
首先在style.xml中設置爲noActionBar的主題,這是必須的app

<style name="fullScreen" parent="Theme.AppCompat.DayNight.NoActionBar">
</style>
複製代碼

方式有三種ide

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_fullscreen_no_text);
    //方式一
    //getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    //方式二
    //getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
    //方式三 style.xml中配置
    //<style name="fullScreen" parent="Theme.AppCompat.DayNight.NoActionBar">
    //        <item name="android:windowFullscreen">true</item>
    //</style>
}
複製代碼

需求2、全屏保留狀態欄文字(頁面上部有Banner圖) 佈局

如今項目,大部分向下支持到19,因此先不考慮過低版本的狀況

Window window = getWindow();
//默認API 最低19 
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
    window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    ViewGroup contentView = window.getDecorView().findViewById(Window.ID_ANDROID_CONTENT);
    contentView.getChildAt(0).setFitsSystemWindows(false);
}
複製代碼

需求3、標題欄與狀態欄顏色一致 xml中配置

<style name="status_toolbar_same_color" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/status_toolBar_same_color</item>
    <item name="colorPrimaryDark">@color/status_toolBar_same_color</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>
複製代碼

咱們能看到這種處理方式,是能夠解決一些業務場景,可是若是在低於21版本手機上就無論用了,那怎麼辦呢?請接着往下看

Window window = getWindow();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.setStatusBarColor(getResources().getColor(R.color.status_toolBar_same_color));
    } else {
        window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        ViewGroup systemContent = findViewById(android.R.id.content);
    
        View statusBarView = new View(this);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight());
        statusBarView.setBackgroundColor(getResources().getColor(R.color.status_toolBar_same_color));
    
        systemContent.getChildAt(0).setFitsSystemWindows(true);
    
        systemContent.addView(statusBarView, 0, lp);
    
    }
複製代碼

適配後的結果:

需求4、不一樣Fragment中對StatusBar的處理不同

  先上圖

<android.support.v7.widget.Toolbar
    android:id="@+id/base_toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@android:color/holo_blue_dark">

    <TextView
        android:id="@+id/base_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textColor="@android:color/black" />
</android.support.v7.widget.Toolbar>

<FrameLayout
    android:id="@+id/base_container"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">

</FrameLayout>
複製代碼

上述代碼是兩個Fragment所依附的Activity對應的部分layout

private void addStatusBar() {
    //條件狀態欄透明,要否則不會起做用
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    if (mStatusBarView == null) {
        mStatusBarView = new View(FragmentStatusAndActionBarActivity.this);
        int screenWidth = getResources().getDisplayMetrics().widthPixels;
        int statusBarHeight = getStatusBarHeight();
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(screenWidth, statusBarHeight);
        mStatusBarView.setLayoutParams(params);
        mStatusBarView.requestLayout();

        //獲取根佈局
        ViewGroup systemContent = findViewById(android.R.id.content);
        ViewGroup userContent = (ViewGroup) systemContent.getChildAt(0);
        userContent.setFitsSystemWindows(false);
        userContent.addView(mStatusBarView, 0);
    }
}
複製代碼

上面是對應Activity中的佈局,意思就是不使用系統提供的ActionBar,使用ToolBar來代替(網上一大推代替的方法),下面的代碼中設置,狀態欄透明,而且設置了sitFitSystemWindow(false),經過這些操做,咱們至關於把系統的StatusBar,ActionBar,都幹掉了,那麼接下來,咱們就能夠模擬建立出StatusBaruserContent.addView(mStatusBarView, 0);那麼如今咱們就能夠本身控制statusBar和ActionBar,顯示什麼顏色?消失仍是隱藏?

ToolBar顯示的Fragment:

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    mActivity.mToolbar.setVisibility(View.VISIBLE);//設置ToolBar顯示
    //設置statusBar的顏色
    mActivity.mStatusBarView.setBackgroundColor(getResources().getColor(android.R.color.holo_blue_bright));
}
複製代碼

ToolBar隱藏的Fragment

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    mActivity.mToolbar.setVisibility(View.GONE);//設置ToolBar消失
    //設置statusBar的顏色
    mActivity.mStatusBarView.setBackgroundColor(getResources().getColor(android.R.color.holo_orange_light));
}
複製代碼

需求5、設置狀態欄文字的顏色

//設置白底黑字
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
複製代碼

可是須要注意的是:目前只有android原生6.0以上支持修改狀態欄字體
除此國內廠商小米、魅族也開放了修改狀態欄字體的方式

小米 MIUI6
魅族 Flyme

需求6、切換fragment時,toolBar和statusbar顯示與否、statusBar顏色、status文字顏色(新增)

評論區,有同窗提出可否"不一樣Fragment中切換狀態欄顏色和狀態欄文字的顏色,甚至同時切換風格(純色狀態欄變成banner往上頂的狀態欄)的狀況",這種狀況確定是沒有問題的,也不難,如今狀態欄和標題欄都是咱們本身,咱們想讓它怎麼樣,它不得乖乖聽話,對不~

先上圖:

其實調整的很少,這裏我只貼下關鍵代碼,gitub代碼倉庫已更新,你們能夠clone看完成代碼

這是隻有Banner的fragment:

@Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //設置ToolBar隱藏
        mActivity.mToolbar.setVisibility(View.GONE);
        //設置statusBar的隱藏
        mActivity.mStatusBarView.setVisibility(View.GONE);
        //恢復默認statusBar文字顏色
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            mActivity.getWindow().getDecorView().setSystemUiVisibility(View.VISIBLE);
        mActivity.mStatusBarView.setVisibility(View.GONE);
    }
複製代碼

改變statusBar字體顏色

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    //設置ToolBar顯示
    mActivity.mToolbar.setVisibility(View.VISIBLE);
    //設置ToolBar的顏色
    mActivity.mToolbar.setBackgroundColor(getResources().getColor(R.color.colorAccent));
    //設置statusBar的顏色
    mActivity.mStatusBarView.setBackgroundColor(getResources().getColor(R.color.colorAccent));
    //設置statusBar顯示
    mActivity.mStatusBarView.setVisibility(View.VISIBLE);
    //設置statusBar字體顏色
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
        mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
複製代碼

思考題

思考1、Activity中window是怎麼回事?裏面有什麼View/ViewGroup

寫了個方法,將整個Window內的View都打印出來了

private void printChildView(ViewGroup viewGroup) {
    Log.i("printView-ViewGroup", viewGroup.getClass().getSimpleName() + "的子View和數量:" + viewGroup.getChildCount());
    for (int i = 0; i < viewGroup.getChildCount(); i++) {
        String simpleName = viewGroup.getChildAt(i).getClass().getSimpleName();
        Log.i("printView-ChildView", simpleName);
    }
    for (int i = 0; i < viewGroup.getChildCount(); i++) {
        if (viewGroup.getChildAt(i) instanceof ViewGroup) {
            printChildView((ViewGroup) viewGroup.getChildAt(i));
        }
    }
}
複製代碼

這是結果

printView-ViewGroup: DecorView的子View和數量:1
printView-ChildView: LinearLayout
printView-ViewGroup: LinearLayout的子View和數量:2
printView-ChildView: ViewStub
printView-ChildView: FrameLayout
printView-ViewGroup: FrameLayout的子View和數量:1
printView-ChildView: ActionBarOverlayLayout
printView-ViewGroup: ActionBarOverlayLayout的子View和數量:2
printView-ChildView: ContentFrameLayout
printView-ChildView: ActionBarContainer
printView-ViewGroup: ContentFrameLayout的子View和數量:2
printView-ChildView: View
printView-ChildView: ConstraintLayout
printView-ViewGroup: ConstraintLayout的子View和數量:1
printView-ChildView: AppCompatTextView
printView-ViewGroup: ActionBarContainer的子View和數量:2
printView-ChildView: Toolbar
printView-ChildView: ActionBarContextView
printView-ViewGroup: Toolbar的子View和數量:1
printView-ChildView: AppCompatTextView
printView-ViewGroup: ActionBarContextView的子View和數量:0
複製代碼

咱們根據結果畫一個分佈圖

上述這個ContentFrameLayout就是咱們Activity中經過setContentView(View)添加的,至於其中的View是咱們本身設備的statusbar,把這個圖畫出來,但願能起一個拋磚引玉的做用,有想法的能夠繼續往下研究,我這裏就不研究了,有想法的能夠評論。

思考2、setFitsSystemWindows()是什麼鬼?

fitsSystemWindows表明的是:當設置SystemBar(包含StatusBar&NavigationBar)透明以後,經過添加flag的方式 將fitsSystemWindows至爲true,則仍是爲SystemBar預留空間,當設置爲false的時候,就是不爲SystemBar預留空間,好比咱們設置狀態欄和標題欄的時候,若是不設置fitSystemWindows爲true的話,就變成了

這確定不是咱們想要的效果,可是爲何會這樣呢?
咱們思考一中,發現ToolBar和咱們的開發者經過setContentView是在一個ActionBarOverlayLayout中,那我就去看看這個View

在系統的 frameworks\support\v7\appcompat\res\layout\abc_screen_toolbar.xml下咱們看到了ActionBarOverlayLayout的佈局

<android.support.v7.internal.widget.ActionBarOverlayLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/decor_content_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

    <include layout="@layout/abc_screen_content_include"/>

    <android.support.v7.internal.widget.ActionBarContainer
            android:id="@+id/action_bar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            style="?attr/actionBarStyle"
            android:touchscreenBlocksFocus="true"
            android:gravity="top">

        <android.support.v7.widget.Toolbar
                android:id="@+id/action_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:navigationContentDescription="@string/abc_action_bar_up_description"
                style="?attr/toolbarStyle"/>

        <android.support.v7.internal.widget.ActionBarContextView
                android:id="@+id/action_context_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:theme="?attr/actionBarTheme"
                style="?attr/actionModeStyle"/>

    </android.support.v7.internal.widget.ActionBarContainer>

</android.support.v7.internal.widget.ActionBarOverlayLayout>
複製代碼

經過includy引入的ContentView abc_screen_content_include.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.internal.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>
複製代碼

layout佈局很普通,沒有什麼特別之處,我看到這時候,猜測:當咱們設置了fitSystemwindow(false),是否是在這個ActionBarOverlayLayoutonLyout()過程會對相應的佈局作調整。而後窮就去他的onLayout()裏看:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeft();
    final int parentRight = right - left - getPaddingRight();

    final int parentTop = getPaddingTop();
    final int parentBottom = bottom - top - getPaddingBottom();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft = parentLeft + lp.leftMargin;
            int childTop = parentTop + lp.topMargin;

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
複製代碼

然而毛都沒有。。。懵逼了,layout的參數都是來自佈局文件裏的,後來我跟着setFitSystemWindow()看到一個方法,就是這個fitSystemWindows(Rect insets)它的註釋說明裏有The content insets tell you the space that the status bar,應該是調用這個方法進行設置的,可是怎麼調用的,目前我尚未找到,但願懂得同窗指點迷津,萬分感謝!

/**
     * Called by the view hierarchy when the content insets for a window have
     * changed, to allow it to adjust its content to fit within those windows.
     * The content insets tell you the space that the status bar, input method,
     * and other system windows infringe on the application's window. ... protected boolean fitSystemWindows(Rect insets) { if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) { if (insets == null) { // Null insets by definition have already been consumed. // This call cannot apply insets since there are none to apply, // so return false. return false; } // If we're not in the process of dispatching the newer apply insets call,
            // that means we're not in the compatibility path. Dispatch into the newer // apply insets path and take things from there. try { mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS; return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed(); } finally { mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS; } } else { // We're being called from the newer apply insets path.
            // Perform the standard fallback behavior.
            return fitSystemWindowsInt(insets);
        }
    }
複製代碼

文章中有任何有異議的地方歡迎提出!


學不盡的技術,作不完的分享!

相關文章
相關標籤/搜索