概述
Android 11 的状态栏与导航栏较之前的版本有较大的差异, 在Android 7.0 SystemUI 状态/导航栏的隐藏与显示中所描述的部分内容已不再适用.
比如, 自动隐藏的时间, 隐藏的动画, 较之前的版本已面目全非, 本文将对隐藏状态栏部分的内容进行一些补充.
APP如何隐藏状态栏
- 参考文档:隐藏状态栏_Android 开发者_Android Developers.pdf)
方法一:
<application
...
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
...
</application>
方法二:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// If the Android version is lower than Jellybean, use this call to hide
// the status bar.
if (Build.VERSION.SDK_INT < 16) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
setContentView(R.layout.activity_main);
}
...
}
方法三:(在4.1(SDK 16)及之上)
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
// Remember that you should never show the action bar if the
// status bar is hidden, so hide that too if necessary.
ActionBar actionBar = getActionBar();
actionBar.hide();
状态栏是怎么被隐藏的
1. 通过上面的方式主动调用接口设置窗口属性, 由系统判断并完成隐藏
frameworks/base/core/java/android/view/InsetsController.java
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller;
if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
if (mDisable) {
onAnimationFinish();
return;
}
mAnimator = ValueAnimator.ofFloat(0f, 1f);
mAnimator.setDuration(mDurationMs);
mAnimator.setInterpolator(new LinearInterpolator());
Insets hiddenInsets = controller.getHiddenStateInsets();
// IME with zero insets is a special case: it will animate-in from offscreen and end
// with final insets of zero and vice-versa.
hiddenInsets = controller.hasZeroInsetsIme()
? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right,
mFloatingImeBottomInset)
: hiddenInsets;
Insets start = mShow
? hiddenInsets
: controller.getShownStateInsets();
Insets end = mShow
? controller.getShownStateInsets()
: hiddenInsets;
Interpolator insetsInterpolator = getInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
float alphaFraction = mShow
? rawFraction
: 1 - rawFraction;
float insetsFraction = insetsInterpolator.getInterpolation(rawFraction);
controller.setInsetsAndAlpha(
sEvaluator.evaluate(insetsFraction, start, end),
alphaInterpolator.getInterpolation(alphaFraction),
rawFraction);
if (DEBUG) Log.d(TAG, "Default animation setInsetsAndAlpha fraction: "
+ insetsFraction);
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
onAnimationFinish();
}
});
if (!mHasAnimationCallbacks) {
mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
}
mAnimator.start();
}
为定位到这一段代码, 走了相当曲折又漫长的路.
- 第一个误区: 参照之前的状态栏隐藏流程代码, 在frameworks/base/service中转了很长时间.
- 第二个误区: 误判隐藏动画的是在SystemUI中实现.
在不断地调试分析后, 最终的突破口在SurfaceControl的子类Transaction
最终输出以下LOG, 最终定位到上面的代码.
2022-12-09 17:56:47.413 at android.view.SurfaceControl$SurfaceControl.setPosition(SurfaceControl.java:2435)
2022-12-09 17:56:47.413 at android.view.SurfaceControl$Transaction.setMatrix(SurfaceControl.java:2609)
2022-12-09 17:56:47.413 at android.view.SyncRtSurfaceTransactionApplier.applyParams(SyncRtSurfaceTransactionApplier.java:97)
2022-12-09 17:56:47.413 at com.android.server.wm.InsetsPolicy$InsetsPolicyAnimationControlListener$InsetsPolicyAnimationControlCallbacks.applySurfaceParams(InsetsPolicy.java:542)
2022-12-09 17:56:47.413 at android.view.InsetsAnimationControlImpl.applyChangeInsets(InsetsAnimationControlImpl.java:214)
2022-12-09 17:56:47.413 at com.android.server.wm.InsetsPolicy$InsetsPolicyAnimationControlListener$InsetsPolicyAnimationControlCallbacks.scheduleApplyChangeInsets(InsetsPolicy.java:510)
2022-12-09 17:56:47.413 at android.view.InsetsAnimationControlImpl.setInsetsAndAlpha(InsetsAnimationControlImpl.java:186)
2022-12-09 17:56:47.413 at android.view.InsetsAnimationControlImpl.setInsetsAndAlpha(InsetsAnimationControlImpl.java:170)
2022-12-09 17:56:47.414 at android.view.InsetsController$InternalAnimationControlListener.lambda$onReady$0$InsetsController$InternalAnimationControlListener(InsetsController.java:332)
2022-12-09 17:56:47.414 at android.view.-$$Lambda$InsetsController$InternalAnimationControlListener$SInf91MjJKDQFXwrp7C-HBi0xaQ.onAnimationUpdate(Unknown Source:13)
2022-12-09 17:56:47.414 at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1566)
2022-12-09 17:56:47.414 at android.animation.ValueAnimator.animateBasedOnTime(ValueAnimator.java:1357)
2022-12-09 17:56:47.414 at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1489)
2022-12-09 17:56:47.414 at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:146)
2022-12-09 17:56:47.414 at android.animation.AnimationHandler.access$100(AnimationHandler.java:37)
2022-12-09 17:56:47.414 at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:54)
2022-12-09 17:56:47.415 at android.view.Choreographer$CallbackRecord.run(Choreographer.java:970)
2022-12-09 17:56:47.415 at android.view.Choreographer.doCallbacks(Choreographer.java:796)
2022-12-09 17:56:47.415 at android.view.Choreographer.doFrame(Choreographer.java:727)
2022-12-09 17:56:47.415 at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
2022-12-09 17:56:47.415 at android.os.Handler.handleCallback(Handler.java:938)
2022-12-09 17:56:47.415 at android.os.Handler.dispatchMessage(Handler.java:99)
2022-12-09 17:56:47.415 at android.os.Looper.loop(Looper.java:223)
2022-12-09 17:56:47.415 at android.os.HandlerThread.run(HandlerThread.java:67)
2022-12-09 17:56:47.415 at com.android.server.ServiceThread.run(ServiceThread.java:44)
状态栏/导航栏 隐藏与显示动画过渡时长:
在定位到代码后, 最先做的事情, 就是把时间增加10倍, 编译看效果.
frameworks/base/core/java/android/view/InsetsController.java
private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340;
2. 隐藏后, 用户下拉状态栏, SystemUI定时执行自动隐藏(AutoHideController)
frameworks/base/services/core/java/com/android/server/wm/InsetsPolicy.java
void hideTransient() {
if (mShowingTransientTypes.size() == 0) {
return;
}
InsetsState state = new InsetsState(mStateController.getRawInsetsState());
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
mShowingTransientTypes.clear();
mStateController.notifyInsetsChanged();
updateBarControlTarget(mFocusedWin);
}
}, state);
mStateController.onInsetsModified(mDummyControlTarget, state);
}
void startAnimation(boolean show, Runnable callback, InsetsState state) {
int typesReady = 0;
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
final IntArray showingTransientTypes = mShowingTransientTypes;
for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
int type = showingTransientTypes.get(i);
InsetsSourceProvider provider = mStateController.getSourceProvider(type);
InsetsSourceControl control = provider.getControl(mDummyControlTarget);
if (control == null || control.getLeash() == null) {
continue;
}
typesReady |= InsetsState.toPublicType(type);
controls.put(control.getType(), new InsetsSourceControl(control));
state.setSourceVisible(type, show);
}
controlAnimationUnchecked(typesReady, controls, show, callback);
}
private void controlAnimationUnchecked(int typesReady,
SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
InsetsPolicyAnimationControlListener listener =
new InsetsPolicyAnimationControlListener(show, callback, typesReady);
listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
}
private class InsetsPolicyAnimationControlListener extends
InsetsController.InternalAnimationControlListener {
Runnable mFinishCallback;
InsetsPolicyAnimationControlCallbacks mControlCallbacks;
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, false /* hasCallbacks */, types, false /* disable */,
(int) (mDisplayContent.getDisplayMetrics().density * FLOATING_IME_BOTTOM_INSET
+ 0.5f));
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
private class InsetsPolicyAnimationControlCallbacks implements
InsetsAnimationControlCallbacks {
...
private void controlAnimationUnchecked(int typesReady,
SparseArray<InsetsSourceControl> controls, boolean show) {
if (typesReady == 0) {
// nothing to animate.
return;
}
mAnimatingShown = show;
mAnimationControl = new InsetsAnimationControlImpl(controls,
mFocusedWin.getDisplayContent().getBounds(), getState(),
mListener, typesReady, this, mListener.getDurationMs(),
InsetsController.SYSTEM_BARS_INTERPOLATOR,
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE);
SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady));
}
一些类图
一些关键代码.
frameworks/base/core/java/android/view/ViewRootImpl.java
case MSG_SHOW_INSETS: {
if (mView == null) {
Log.e(TAG,
String.format("Calling showInsets(%d,%b) on window that no longer"
+ " has views.", msg.arg1, msg.arg2 == 1));
}
clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
mInsetsController.show(msg.arg1, msg.arg2 == 1);
break;
}
private void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
return;
}
final int sysUiVis = params.systemUiVisibility | params.subtreeSystemUiVisibility;
final int flags = params.flags;
final boolean matchParent = params.width == MATCH_PARENT && params.height == MATCH_PARENT;
final boolean nonAttachedAppWindow = params.type >= FIRST_APPLICATION_WINDOW
&& params.type <= LAST_APPLICATION_WINDOW;
final boolean statusWasHiddenByFlags = (mTypesHiddenByFlags & Type.statusBars()) != 0;
final boolean statusIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| ((flags & FLAG_FULLSCREEN) != 0 && matchParent && nonAttachedAppWindow);
final boolean navWasHiddenByFlags = (mTypesHiddenByFlags & Type.navigationBars()) != 0;
final boolean navIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
@InsetsType int typesToHide = 0;
@InsetsType int typesToShow = 0;
if (statusIsHiddenByFlags && !statusWasHiddenByFlags) {
typesToHide |= Type.statusBars();
} else if (!statusIsHiddenByFlags && statusWasHiddenByFlags) {
typesToShow |= Type.statusBars();
}
if (navIsHiddenByFlags && !navWasHiddenByFlags) {
typesToHide |= Type.navigationBars();
} else if (!navIsHiddenByFlags && navWasHiddenByFlags) {
typesToShow |= Type.navigationBars();
}
if (typesToHide != 0) {
getInsetsController().hide(typesToHide);
}
if (typesToShow != 0) {
getInsetsController().show(typesToShow);
}
mTypesHiddenByFlags |= typesToHide;
mTypesHiddenByFlags &= ~typesToShow;
}
参考
隐藏状态栏| Android 开发者
Android 显示、隐藏状态栏和导航栏
SurfaceFlinger中Layer的修改 - 安卓R