概述
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