Android(安卓)windowTranslucentStatus属性源码分析
简介
我们在设置系统样式时,将windowTranslucentStatus
和windowTranslucentNavigation
属性设置为true
后,Activity就会显示为如下效果:
状态栏和导航栏都会显示成半透明的状态。并且布局会拓展到系统栏的后面。本文就是要从源码分析windowTranslucentStatus
的实现原理。因为windowTranslucentNavigation
是一样的原理所以就不再去分析,我们只要理解了windowTranslucentStatus
实现流程,自然而然也就知道了windowTranslucentNavigation
的实现原理。当然,这是Android 4.4后才有的属性接口,需要注意!!!
res/values/styles.xml
当然也可以在代码中动态设置
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); }
源码分析
因为讲的东西比较多,跨度比较大,代码量也比较大,先来一张时序图,有一个大概的印象。然后,再一一分解之。
上图是个人根据代码调用的流程画的一张时序图,可以不是很规范。大概还是能表现出整个SystemUI变化调用的流程的,先凑合着看。
第一步,我们在styles.xml中设置了windowTranslucentStatus
为true
。它真正的实现是在PhoneWindow.java
的generateLayout
方法中。代码如下:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); if (a.getBoolean(com.android.internal.R.styleable.Window_windowTranslucentStatus, false)) { setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS & (~getForcedWindowFlags())); }}
frameworks/base/core/java/android/view/Window.java
public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes(); attrs.flags = (attrs.flags&~mask) | (flags&mask); if ((mask&WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0) { attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; } mForcedWindowFlags |= mask; if (mCallback != null) { mCallback.onWindowAttributesChanged(attrs); }}
上面代码可以看出我们在styles.xml中设置的属性,会通过setFlags设置下去。而setFlags其实就是将属性设置给了WindowManager.LayoutParams
。也就是说,我们设置的windowTranslucentStatus
属性,最终变成WindowManager.LayoutParams
flags属性中FLAG_TRANSLUCENT_STATUS
。那么这个flags属性对于我们的状态栏有什么实际影响呢,接着往下看。
第二步,更新状态栏的状态变化。在PhoneWindowManager.java
中updateSystemUiVisibilityLw
方法是处理系统状态栏变化的地方。在其内部它会调用updateSystemBarsLw
方法,然后,根据其返回值设置给SystemUI。而updateSystemBarsLw
中mStatusBarController
才是真正管理状态栏的实例类。在updateSystemBarsLw
中,它会调用mStatusBarController
的applyTranslucentFlagLw
和updateVisibilityLw
的方法。这两个方法就会返回相应的显示状态。SystemUI就是根据这个显示状态值,做相应的变化。
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
private final BarController mStatusBarController = new BarController("StatusBar", View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_UNHIDE, View.STATUS_BAR_TRANSLUCENT, StatusBarManager.WINDOW_STATUS_BAR, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);private int updateSystemUiVisibilityLw() {... final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility); mFocusedApp = win.getAppToken(); mHandler.post(new Runnable() { @Override public void run() { try { IStatusBarService statusbar = getStatusBarService(); if (statusbar != null) { //改变SystemUI的地方 statusbar.setSystemUiVisibility(visibility, 0xffffffff); statusbar.topAppWindowChanged(needsMenu); } } catch (RemoteException e) { // re-acquire status bar service next time it is needed. mStatusBarService = null; } } });}private int updateSystemBarsLw(WindowState win, int oldVis, int vis) { // apply translucent bar vis flags WindowState transWin = mKeyguard != null && mKeyguard.isVisibleLw() && !mHideLockScreen ? mKeyguard : mTopFullscreenOpaqueWindowState; vis = mStatusBarController.applyTranslucentFlagLw(transWin, vis, oldVis); ... vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis); ... return vis;}
上面的代码主要是通过mStatusBarController
来处理状态栏的显示状态。现在我们来看看它对应的实现方法。在applyTranslucentFlagLw
方法中,就真正的联系上了,我们之前设置的windowTranslucentStatus
属性。为什么这么说?因为applyTranslucentFlagLw
方法体有一个if ((win.getAttrs().flags & mTranslucentWmFlag) != 0)
判断。这个判断其实就是判断我们有没有把windowTranslucentStatus
属性设置为true
。win.getAttrs().flags
对应的我们设置的WindowManager.LayoutParams
的flags,而mTranslucentWmFlag
就等于WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
。所以,这里就和第一步联系起来了。如果我们设置了windowTranslucentStatus
为true
。这个if
判断就为true
,vis就增加一个mTranslucentFlag
标志,其值为View.STATUS_BAR_TRANSLUCENT
。这个标志很重要,我们现在先放到这,后面会用这个标志。这个新vis就会返回回去。updateVisibilityLw
方法没有做什么很重要的事情就忽略不分析了。
frameworks/base/policy/src/com/android/internal/policy/impl/BarController.java
public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag, int statusBarManagerId, int translucentWmFlag) { mTag = "BarController." + tag; //tag="StatusBar" mTransientFlag = transientFlag; //transientFlag=View.STATUS_BAR_TRANSIENT mUnhideFlag = unhideFlag; //unhideFlag=View.STATUS_BAR_UNHIDE mTranslucentFlag = translucentFlag;//translucentFlag=View.STATUS_BAR_TRANSLUCENT, mStatusBarManagerId = statusBarManagerId; //statusBarManagerId=StatusBarManager.WINDOW_STATUS_BAR mTranslucentWmFlag = translucentWmFlag; //translucentWmFlag=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS mHandler = new Handler();}public int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) { if (mWin != null) { if (win != null && (win.getAttrs().privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) == 0) { //mTranslucentWmFlag=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS //如果我们设置了FLAG_TRANSLUCENT_STATUS属性,vis就会添加一个mTranslucentFlag标记,即View.STATUS_BAR_TRANSLUCENT if ((win.getAttrs().flags & mTranslucentWmFlag) != 0) { vis |= mTranslucentFlag; } else { vis &= ~mTranslucentFlag; } } else { vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag); } } return vis;}public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) { if (mWin == null) return vis; if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested if (transientAllowed) { vis |= mTransientFlag; if ((oldVis & mTransientFlag) == 0) { vis |= mUnhideFlag; // tell sysui we're ready to unhide } setTransientBarState(TRANSIENT_BAR_SHOWING); // request accepted } else { setTransientBarState(TRANSIENT_BAR_NONE); // request denied } } if (mTransientBarState != TRANSIENT_BAR_NONE) { vis |= mTransientFlag; // ignore clear requests until transition completes vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile } if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0) { mLastTranslucent = SystemClock.uptimeMillis(); } return vis;}
第三步,updateSystemBarsLw
调用结束后,接下来就是调用statusbar.setSystemUiVisibility(visibility, 0xffffffff);
,这才是真正改变SystemUI状态的接口。visibility
就是updateSystemBarsLw
的返回值,其实也就是mStatusBarController.applyTranslucentFlagLw
和mStatusBarController.updateVisibilityLw
的返回值。而statusbar
调用的是StatusBarManagerService.java
中的setSystemUiVisibility
方法,它最终调用的是mBar.setSystemUiVisibility(vis, mask)
,那这个mBar
又是谁传过来的呢?从代码可以看出,这个mBar是通过registerStatusBar
注册而来的。哪谁又调用了这个registerStatusBar
方法?
frameworks/base/services/java/com/android/server/StatusBarManagerService.java
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, List notificationKeys, List notifications, int switches[], List binders) { enforceStatusBarService(); Slog.i(TAG, "registerStatusBar bar=" + bar); mBar = bar;}public void setSystemUiVisibility(int vis, int mask) { // also allows calls from window manager which is in this process. enforceStatusBarService(); if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")"); synchronized (mLock) { updateUiVisibilityLocked(vis, mask); disableLocked(mCurrentUserId, vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken, "WindowManager.LayoutParams"); }}private void updateUiVisibilityLocked(final int vis, final int mask) { if (mSystemUiVisibility != vis) { mSystemUiVisibility = vis; mHandler.post(new Runnable() { public void run() { if (mBar != null) { try { mBar.setSystemUiVisibility(vis, mask); } catch (RemoteException ex) { } } } }); }}
第四步,SystemUI闪亮登场,真正实现SystemUI变化的主角。它在BaseStatusBar.java
中调用了StatusBarManagerService.java
的registerStatusBar
将CommandQueue
注册到StatusBarManagerService
与其产生关联。所以StatusBarManagerService
中setSystemUiVisibility
最终调用的是CommandQueue.java
中方法。而CommandQueue.java
和BaseStatusBar.java
是通过接口回调实现对应关系的,PhoneStatusBar.java
继承自BaseStatusBar.java
。所以CommandQueue
调用setSystemUiVisibility
方法,最终调用的是PhoneStatusBar
中的setSystemUiVisibility
方法。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
public void start() {... // Connect in to the status bar manager service StatusBarIconList iconList = new StatusBarIconList(); ArrayList notificationKeys = new ArrayList(); ArrayList notifications = new ArrayList(); mCommandQueue = new CommandQueue(this, iconList); try { mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, switches, binders); } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. }...}
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
public CommandQueue(Callbacks callbacks, StatusBarIconList list) { mCallbacks = callbacks; mList = list;}public void setSystemUiVisibility(int vis, int mask) { synchronized (mList) { mHandler.removeMessages(MSG_SET_SYSTEMUI_VISIBILITY); mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget(); }}private final class H extends Handler { public void handleMessage(Message msg) { final int what = msg.what & MSG_MASK; switch (what) { case MSG_SET_SYSTEMUI_VISIBILITY: mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2); break; ... } }}
第五步,PhoneStatusBar.java
中setSystemUiVisibility
的实现。这个方法真正关键地方是在调用barMode
这个方法中获取最新的mode状态。它会用vis比较各个flag,vis就是我们在PhoneWindowManager.java
中传入的参数。我们知道在第二步后面增加了View.STATUS_BAR_TRANSLUCENT这个标志,说了很重要。在这里才真正的体现出来了。barMode
方法通过比较得到mode
为MODE_TRANSLUCENT
。然后,通过checkBarModes
应用相应的mode。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@Override // CommandQueuepublic void setSystemUiVisibility(int vis, int mask) { final int oldVal = mSystemUiVisibility; final int newVal = (oldVal&~mask) | (vis&mask); final int diff = newVal ^ oldVal; ... // update status bar mode final int sbMode = computeBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(), View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_TRANSLUCENT); final boolean sbModeChanged = sbMode != -1; boolean checkBarModes = false; if (sbModeChanged && sbMode != mStatusBarMode) { mStatusBarMode = sbMode; checkBarModes = true; } if (checkBarModes) { checkBarModes(); }}private int computeBarMode(int oldVis, int newVis, BarTransitions transitions, int transientFlag, int translucentFlag) { final int oldMode = barMode(oldVis, transientFlag, translucentFlag); final int newMode = barMode(newVis, transientFlag, translucentFlag); if (oldMode == newMode) { return -1; // no mode change } return newMode;}private int barMode(int vis, int transientFlag, int translucentFlag) { return (vis & transientFlag) != 0 ? MODE_SEMI_TRANSPARENT : (vis & translucentFlag) != 0 ? MODE_TRANSLUCENT : (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0 ? MODE_LIGHTS_OUT : MODE_OPAQUE;}private void checkBarModes() { if (mDemoMode) return; int sbMode = mStatusBarMode; if (panelsEnabled() && (mInteractingWindows & StatusBarManager.WINDOW_STATUS_BAR) != 0) { // if panels are expandable, force the status bar opaque on any interaction sbMode = MODE_OPAQUE; } checkBarMode(sbMode, mStatusBarWindowState, mStatusBarView.getBarTransitions()); if (mNavigationBarView != null) { checkBarMode(mNavigationBarMode, mNavigationBarWindowState, mNavigationBarView.getBarTransitions()); }}private void checkBarMode(int mode, int windowState, BarTransitions transitions) { final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN; transitions.transitionTo(mode, anim);}
在checkBarMode
中transitions
是从PhoneStatusBarView
中创建的PhoneStatusBarTransitions
。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); mBarTransitions = new PhoneStatusBarTransitions(this);}public BarTransitions getBarTransitions() { return mBarTransitions;}
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
public PhoneStatusBarTransitions(PhoneStatusBarView view) { super(view, R.drawable.status_background); mView = view;}
PhoneStatusBarTransitions
继承自BarTransitions
。通过PhoneStatusBarTransitions
的构造函数,我们看到其调用了父类的构造方法super(view, R.drawable.status_background);
。在其父类的构造方法中会将R.drawable.status_background
这个资源图片设置到view的背景中去。那R.drawable.status_background
是个什么样的图片呢?如下图:
是不是恍然大悟,原来我们看到的状态渐变效果就是因为设置这张点9的背景图片。那问题来,我们的状态栏不应该一直都是这种渐变效果吗?哈哈,是不是被忽悠了,客官别急,还有最后一步。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
public BarTransitions(View view, int gradientResourceId) { mTag = "BarTransitions." + view.getClass().getSimpleName(); mView = view; mBarBackground = new BarBackgroundDrawable(mView.getContext(), radientResourceId); if (HIGH_END) { mView.setBackground(mBarBackground); }}public void transitionTo(int mode, boolean animate) { // low-end devices do not support translucent modes, fallback to opaque if (!HIGH_END && (mode == MODE_SEMI_TRANSPARENT || mode == MODE_TRANSLUCENT)) { mode = MODE_OPAQUE; } if (mMode == mode) return; int oldMode = mMode; mMode = mode; if (DEBUG) Log.d(mTag, String.format("%s -> %s animate=%s", modeToString(oldMode), modeToString(mode), animate)); onTransition(oldMode, mMode, animate);}protected void onTransition(int oldMode, int newMode, boolean animate) { if (HIGH_END) { applyModeBackground(oldMode, newMode, animate); }}protected void applyModeBackground(int oldMode, int newMode, boolean animate) { if (DEBUG) Log.d(mTag, String.format("applyModeBackground oldMode=%s newMode=%s animate=%s", modeToString(oldMode), modeToString(newMode), animate)); mBarBackground.applyModeBackground(oldMode, newMode, animate);}private static class BarBackgroundDrawable extends Drawable { public BarBackgroundDrawable(Context context, int gradientResourceId) { final Resources res = context.getResources(); mGradient = res.getDrawable(gradientResourceId); mInterpolator = new LinearInterpolator(); } public void applyModeBackground(int oldMode, int newMode, boolean animate) { if (mMode == newMode) return; mMode = newMode; mAnimating = animate; if (animate) { long now = SystemClock.elapsedRealtime(); mStartTime = now; mEndTime = now + BACKGROUND_DURATION; mGradientAlphaStart = mGradientAlpha; mColorStart = mColor; } invalidateSelf(); } @Override public void draw(Canvas canvas) { int targetGradientAlpha = 0, targetColor = 0; if (mMode == MODE_TRANSLUCENT) { targetGradientAlpha = 0xff; } else if (mMode == MODE_SEMI_TRANSPARENT) { targetColor = mSemiTransparent; } else { targetColor = mOpaque; } if (!mAnimating) { mColor = targetColor; mGradientAlpha = targetGradientAlpha; } else { ... } if (mGradientAlpha > 0) { mGradient.setAlpha(mGradientAlpha); mGradient.draw(canvas); } if (Color.alpha(mColor) > 0) { canvas.drawColor(mColor); } if (mAnimating) { invalidateSelf(); // keep going } } }
最后,我们再来看下PhoneStatusBar.java
中checkBarMode
方法的transitions.transitionTo
调用。这个transitionTo
最终调用的是BarTransitions.java
中的transitionTo
方法,通过上面的源码可以知道它其实最后调用就是BarBackgroundDrawable
中的applyModeBackground
方法。会传入mode,·并调用invalidateSelf
方法。最终在draw中,根据mode显示对应的背景效果。这里因为我们的mode为MODE_TRANSLUCENT
,所以,最终会调用mGradient.draw(canvas);
。而mGradient
就是这个R.drawable.status_background
图片的drawable,所以我们在mode为MODE_TRANSLUCENT
就会显示为一个渐变的效果。而在其它mode时候显示一个rgb颜色背景。
额外补充
有没有发现我们在设置windowTranslucentStatus
和windowTranslucentNavigation
为ture
后,我们的布局也会拓展到状态栏和导航栏后面去。原因在这:
frameworks/base/core/java/android/view/ViewRootImpl.java
private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) { int vis = 0; // Translucent decor window flags imply stable system ui visibility. if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) { vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; } if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) { vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } return vis;}
系统在我们添加windowTranslucentStatus
和windowTranslucentNavigation
属性时候,会自动为我们增加View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
属性。
那要怎么解决我们的布局会被拓展到系统栏后面的效果。在layout.xml增加android:fitsSystemWindows="true"
即可。效果如下:
总结
写这篇文章中途有很多次试图放弃了,因为要把这些知识用文字表达出来确实很为难自己啊。表达能力本来就不是特别好,再加上用文字的方法更难了。自己虽然已经明白了整个实现流程。但是变向让自己去把他们写出来还是难以下笔。最终还是咬咬牙写下来了,算是对这个知识点的一个总结吧。
更多相关文章
- Android图形---OpenGL(一)
- Android(安卓)第十三课——ListView ListActivity SimpleAdapter
- Android(安卓)中的单元测试
- android 视图结构 呈现给用户的视图
- android软键盘状态监听最稳的方法,属性动画手动调整布局,再也不怕
- Android中的SrollView滚动详解
- Android(安卓)完全退出应用方法
- 字节跳动面试官:Android源码的Binder权限是如何控制?
- 如何隐藏APP名字