前言
在Android系统中,很多应用都需要根据具体情况来控制状态栏和导航栏的显示和隐藏,又或者将状态栏透明,实现诸如沉浸式、全面屏灯效果,而要实现这些效果,都离不开SystemUIVisibility属性。由于SystemUIVisibilityy属性主要用来控制系统状态栏和导航栏的行为,而状态栏和导航栏都属于SystemUI模块的StatusBar,所以SystemUIVisibility属性的消费者肯定包含StatusBar。另外当状态栏和导航栏发生变化的时候,窗口的布局一般也会跟着发生变化,这就意味着窗口管理者PhoneWindowManager肯定也要消费SystemUIVisibility属性。本篇文章我们就来具体分析一下和这个属性有关的代码。
一、控制状态栏和底部栏行为
1.1、使用SystemUIVisibility属性控制
1、为窗口设置SystemUIVisibility属性的方式有两种,一种是直接在窗口的WindowManager.LayoutParams对象的systemUiVisibility属性上进行设置,并通过WindowManager.updateViewLayout()方法使其生效。
frameworks/base/core/java/android/view/WindowManager.java
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//隐藏窗口的所有装饰,比如状态栏和导航栏
public static final int FLAG_FULLSCREEN = 0x00000400;
//控制窗口状态栏、导航栏的显示和隐藏
public int systemUiVisibility;
}
}
2、另一种是在一个已经显示在窗口上的控件中调用setSystemUiVisibility方法,传入如下属性。
frameworks/base/core/java/android/view/View.java
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
//隐藏导航栏
public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
//隐藏状态栏
public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
}
这种方式最终影响的其实是窗口的WindowManager.LayoutParams对象的subtreeSystemUiVisibility属性。
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//控制窗口状态栏、导航栏的显示和隐藏
public int subtreeSystemUiVisibility;
}
}
窗口的状态栏导航栏显示与否,最终其实是受以上两个属性共同影响的。
1.2、使用PhoneWindow的setDecorFitsSystemWindows方法
页面想要实现状态栏和底部栏的沉浸式效果,除了使用SystemUIVisibility属性,还可以调用当前页面对应的Window的setDecorFitsSystemWindows方法。
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//窗口的内容区域是否自适应系统装饰窗口(例如状态栏和导航栏)的显示区域。
//如果设置为true,当前窗口的内容将自动避开系统装饰窗口区域,以避免视图内容被系统栏遮挡。
boolean mDecorFitsSystemWindows = true;
/**
* @param decorFitsSystemWindows false沉浸式系统栏 true非沉浸式系统栏
*/
@Override
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
mDecorFitsSystemWindows = decorFitsSystemWindows;
applyDecorFitsSystemWindows();
}
}
二、View的setSystemUiVisibility方法调用流程
1、View的setSystemUiVisibility方法如下所示。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
int mSystemUiVisibility;
protected ViewParent mParent;
public void setSystemUiVisibility(int visibility) {
if (visibility != mSystemUiVisibility) {
mSystemUiVisibility = visibility;//保存SystemUIVisibility属性
if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
mParent.recomputeViewAttributes(this);//通知父控件子控件属性发生了变化
}
}
}
}
setSystemUiVisibility方法首先将属性赋值给mSystemUiVisibility,然后会调用父控件的recomputeViewAttributes方法,通知父控件子控件属性发生了变化。ViewParent是一个接口,在Android中有两个类实现了这个接口,它们分别是ViewGroup和ViewRootImpl。
2、ViewGroup和ViewRootImpl和recomputeViewAttributes方法相关的代码如下所示。
frameworks/base/core/java/android/view/View.java
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public void recomputeViewAttributes(View child) {
if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
ViewParent parent = mParent;
if (parent != null) parent.recomputeViewAttributes(this);
}
}
}
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final View.AttachInfo mAttachInfo;
@Override
public void recomputeViewAttributes(View child) {
checkThread();//检测线程是不是UI线程
if (mView == child) {
mAttachInfo.mRecomputeGlobalAttributes = true;//标记需要重新计算本地属性
if (!mWillDrawSoon) {
scheduleTraversals();//进一步调用scheduleTraversals方法。
}
}
}
}
结合Android 12系统源码_窗口管理(二)WindowManager对窗口的管理过程,我们知道包括Activity的根布局DecorView在内的任何View,WindowManager在将它添加到窗口上的过程中,最终都会创建一个ViewRootImpl,并将View设置给ViewRootImpl,这样根View的父类就变成了ViewRootImpl。这就意味着不管任何子View调用recomputeViewAttributes方法,最终所触发的都是ViewRootImpl的recomputeViewAttributes,而ViewRootImpl会进一步调用scheduleTraversals方法。
3、ViewRootImpl和scheduleTraversals方法相关的代码如下所示。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final Choreographer mChoreographer;//编舞者
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();//回调对象
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();//继续执行doTraversal
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//执行performTraversals
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
}
scheduleTraversals方法会为编舞者对象设置回调,最终会等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法,该方法会调用doTraversal方法,然后进一步调用performTraversals方法。
4、ViewRootImpl的performTraversals方法代码逻辑非常多,这里只列出了我们需要关注的代码。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {
final IWindowSession mWindowSession;//和WMS通信的Binder对象,具体为Session对象
public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
final View.AttachInfo mAttachInfo;//控件信息
private void performTraversals() {
final View host = mView;
if (host == null || !mAdded) {
return;
}
if (mWaitForBlastSyncComplete) {
mRequestedTraverseWhilePaused = true;
return;
}
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
WindowManager.LayoutParams lp = mWindowAttributes;//将当前窗口的最新属性赋值给lp
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
|| mAppVisibilityChanged);
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
WindowManager.LayoutParams params = null;
...代码省略...
//收集mView的属性,判断是否需要更新params
if (collectViewAttributes()) {
params = lp;
}
...代码省略...
//此方法最终会触发WindowManagerService的relayoutWindow方法
relayoutWindow(params, viewVisibility, insetsPending);
...代码省略...
//测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...代码省略...
//布局
performLayout(lp, mWidth, mHeight);
...代码省略...
//绘制
performDraw(</