最近手頭做了一個新的專案,開發中對狀態列的要求比較多,也作了一些總結,分享給大家。
簡答題
- 全屏、不保留狀態列文字(Splash頁面,歡迎頁面)
- 全屏保留狀態列文字(頁面上部有Banner圖)
- 標題欄與狀態列顏色一致(部分App風格)
- 不同Fragment中對StatusBar的處理不一樣
- 設定狀態列文字的顏色
- 切換fragment時,toolBar顯示與否、statusbar顯示與否、statusBar顏色、statusBar文字顏色(新增)
思考題
- Activity中window是怎麼回事?裡面有什麼View/ViewGroup?
- setFitsSystemWindows()是什麼鬼?
簡答題,是本篇文章闡述的內容;思考題,是針對所闡述的內容做一些擴充,反應兩個層面:怎麼開發?為什麼能實現這樣的功能?
簡答題
需求一、全屏,不保留狀態列文字(Splash頁面,歡迎頁面)
這個效果大家腦補下,就不貼圖了
首先在style.xml中設定為noActionBar的主題,這是必須的
<style name="fullScreen" parent="Theme.AppCompat.DayNight.NoActionBar">
</style>
複製程式碼
方式有三種
@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>
}
複製程式碼
需求二、全屏保留狀態列文字(頁面上部有Banner圖)
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/e1c7b2d03f906f878d4c0cf431b27a171f541b102fd2b8ec6fd47f0aa60be0ff.png)
現在專案,大部分向下支援到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);
}
複製程式碼
需求三、標題欄與狀態列顏色一致 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>
複製程式碼
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/a6226ff67995f8570596460c4c57bd11483d620ff850927371e3f22ba3524077.png)
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);
}
複製程式碼
適配後的結果:
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/8042657f036196f70265f8a9f7f53197bf25ecaa30205c7218c263aa07a8ce51.png)
需求四、不同Fragment中對StatusBar的處理不一樣
先上圖
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/601106927a810815cc399b454aaada361eedda801a284900fde30bc2ea6ed39d.gif)
<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));
}
複製程式碼
需求五、設定狀態列文字的顏色
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/37fe40811028bcc08e8760f73e534394e3c829537a68863b4507d221a2f7614c.png)
//設定白底黑字
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
複製程式碼
但是需要注意的是:目前只有android原生6.0以上支援修改狀態列字型
除此國內廠商小米、魅族也開放了修改狀態列字型的方式
需求六、切換fragment時,toolBar和statusbar顯示與否、statusBar顏色、status文字顏色(新增)
評論區,有同學提出能否"不同Fragment中切換狀態列顏色和狀態列文字的顏色,甚至同時切換風格(純色狀態列變成banner往上頂的狀態列)的情況",這種情況肯定是沒有問題的,也不難,現在狀態列和標題欄都是我們自己,我們想讓它怎麼樣,它不得乖乖聽話,對不~
先上圖:
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/feee022f29f9229a2e0038ffe8ca6766241f77ff98b29524bd42f6029e5fb3fb.gif)
其實調整的不多,這裡我只貼下關鍵程式碼,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);
}
複製程式碼
思考題
思考一、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
複製程式碼
我們根據結果畫一個分佈圖
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/f12c254934638851d0382403377bf8ac8bd7dee669e0e1cd4120d150b4ffc7f3.png)
上述這個ContentFrameLayout就是我們Activity中通過setContentView(View)新增的,至於其中的View是我們自己裝置的statusbar,把這個圖畫出來,希望能起一個拋磚引玉的作用,有想法的可以繼續往下研究,我這裡就不研究了,有想法的可以評論。
思考二、setFitsSystemWindows()是什麼鬼?
fitsSystemWindows代表的是:當設定SystemBar(包含StatusBar&NavigationBar)透明之後,通過新增flag的方式 將fitsSystemWindows至為true,則還是為SystemBar預留空間,當設定為false的時候,就是不為SystemBar預留空間,比如我們設定狀態列和標題欄的時候,如果不設定fitSystemWindows為true的話,就變成了
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/a1162f199361924802b3f3ce06e07d06cbe1c610afd6703275bce3ed50eb44d4.png)
我們思考一中,發現ToolBar和我們的開發者通過setContentView是在一個ActionBarOverlayLayout中,那我就去看看這個View
![Android 狀態列關於開發的幾件事](https://i.iter01.com/images/179ad12c75e5801b8e439e5b4a2f4626c6f9f49ca241342397d19eb342d96bf0.png)
<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),是不是在這個ActionBarOverlayLayout
的onLyout()
過程會對相應的佈局做調整。然後窮就去他的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);
}
}
複製程式碼
文章中有任何有異議的地方歡迎提出!
學不盡的技術,做不完的分享!