最近手頭作了一個新的項目,開發中對狀態欄的要求比較多,也做了一些總結,分享給你們。php
簡答題html
思考題android
簡答題,是本篇文章闡述的內容;思考題,是針對所闡述的內容作一些拓展,反應兩個層面:怎麼開發?爲何能實現這樣的功能?
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以上支持修改狀態欄字體
除此國內廠商小米、魅族也開放了修改狀態欄字體的方式
需求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的話,就變成了
這確定不是咱們想要的效果,可是爲何會這樣呢?<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);
}
}
複製代碼
文章中有任何有異議的地方歡迎提出!
學不盡的技術,作不完的分享!