简介

我们在设置系统样式时,将windowTranslucentStatuswindowTranslucentNavigation属性设置为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中设置了windowTranslucentStatustrue。它真正的实现是在PhoneWindow.javagenerateLayout方法中。代码如下:

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.LayoutParamsflags属性中FLAG_TRANSLUCENT_STATUS。那么这个flags属性对于我们的状态栏有什么实际影响呢,接着往下看。

第二步,更新状态栏的状态变化。在PhoneWindowManager.javaupdateSystemUiVisibilityLw方法是处理系统状态栏变化的地方。在其内部它会调用updateSystemBarsLw方法,然后,根据其返回值设置给SystemUI。而updateSystemBarsLwmStatusBarController才是真正管理状态栏的实例类。在updateSystemBarsLw中,它会调用mStatusBarControllerapplyTranslucentFlagLwupdateVisibilityLw的方法。这两个方法就会返回相应的显示状态。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属性设置为truewin.getAttrs().flags对应的我们设置的WindowManager.LayoutParams的flags,而mTranslucentWmFlag就等于WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS。所以,这里就和第一步联系起来了。如果我们设置了windowTranslucentStatustrue。这个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.applyTranslucentFlagLwmStatusBarController.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.javaregisterStatusBarCommandQueue注册到StatusBarManagerService与其产生关联。所以StatusBarManagerServicesetSystemUiVisibility最终调用的是CommandQueue.java中方法。而CommandQueue.javaBaseStatusBar.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.javasetSystemUiVisibility的实现。这个方法真正关键地方是在调用barMode这个方法中获取最新的mode状态。它会用vis比较各个flag,vis就是我们在PhoneWindowManager.java中传入的参数。我们知道在第二步后面增加了View.STATUS_BAR_TRANSLUCENT这个标志,说了很重要。在这里才真正的体现出来了。barMode方法通过比较得到modeMODE_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);}

checkBarModetransitions是从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.javacheckBarMode方法的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颜色背景。

额外补充

有没有发现我们在设置windowTranslucentStatuswindowTranslucentNavigationture后,我们的布局也会拓展到状态栏和导航栏后面去。原因在这:

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;}

系统在我们添加windowTranslucentStatuswindowTranslucentNavigation属性时候,会自动为我们增加View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION属性。

那要怎么解决我们的布局会被拓展到系统栏后面的效果。在layout.xml增加android:fitsSystemWindows="true"即可。效果如下:

总结

写这篇文章中途有很多次试图放弃了,因为要把这些知识用文字表达出来确实很为难自己啊。表达能力本来就不是特别好,再加上用文字的方法更难了。自己虽然已经明白了整个实现流程。但是变向让自己去把他们写出来还是难以下笔。最终还是咬咬牙写下来了,算是对这个知识点的一个总结吧。

更多相关文章

  1. Android图形---OpenGL(一)
  2. Android(安卓)第十三课——ListView ListActivity SimpleAdapter
  3. Android(安卓)中的单元测试
  4. android 视图结构 呈现给用户的视图
  5. android软键盘状态监听最稳的方法,属性动画手动调整布局,再也不怕
  6. Android中的SrollView滚动详解
  7. Android(安卓)完全退出应用方法
  8. 字节跳动面试官:Android源码的Binder权限是如何控制?
  9. 如何隐藏APP名字

随机推荐

  1. Mac下Android源码下载教程
  2. Android会根据内容自动变色的TextView
  3. python获取android设备的GPS信息脚本分享
  4. Android开发中用到的命令——不常用就忘
  5. Android(安卓)Toast 总结
  6. Android http请求使用接口回调
  7. 一行代码搞定三级缓存
  8. Android使用GridLayout布局简单的计算器
  9. Android系统源码阅读(11):Android的InputMan
  10. android通过JNI用C/C++创建本地文件