简介
我们在设置系统样式时,将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().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, 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.LayoutParamsflags属性中
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 // CommandQueue
public 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"即可。效果如下: