Android的沉浸式解析
-
概述
默认的,app的内容总是填充在状态栏之下&导航栏之上,状态栏和导航栏被称为系统栏,沉浸式就是把app的内容延伸到系统栏,并且要控制内容分布不要被系统栏的信息遮盖,而且不能和系统的手势冲突,比如状态栏的下拉和导航栏的上滑。
在开发过程中,你可能遇到过为什么fitSystemWindow有的版本有效果有的没效果或者达不到想要的效果的问题,不知道怎么完美解决沉浸式的实现,你可能也会使用第三方的框架来实现,比如QMUIWindowLayout,但是又不明白它的源码逻辑为什么要那么写,我们今天就来把这个骨头啃一啃。
-
官方沉浸式做法
-
将内容延伸到系统栏:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) }
-
经过第一步之后,如果系统栏有颜色则会覆盖住主内容,所以要修改系统栏背景色为透明,从而让应用内容显示出来。
其次,系统栏的图标等信息颜色可能会和背景融为一体,这是需要修改系统栏内容(通知图标等)的颜色来避免和应用内容颜色重叠。
<style name="Theme.MyApp"> <!-- 修改系统栏背景色为透明,values-v19/themes.xml --> <item name="android:windowTranslucentStatus">true</item> <item name="android:windowTranslucentNavigation">true</item> <!-- 修改系统栏背景色为透明,values-v21/themes.xml --> <item name="android:navigationBarColor"> @android:color/transparent </item> <item name="android:statusBarColor"> @android:color/transparent </item> <!-- 修改系统栏内容(通知图标等)的颜色 values-v23/themes.xml Optional: set the status bar light and content dark. --> <item name="android:windowLightStatusBar"> true </item> </style>
对于windowLightStatusBar来说,你也可以通过代码动态地修改系统栏的内容配色模式,只有两种,一种亮色模式一种暗色模式:
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView) windowInsetsController?.isAppearanceLightNavigationBars = true
对于4.4到5.0之间的版本来说,只能指定android:windowTranslucentXxx属性来使系统栏透明,但是会有一个渐变阴影存在:
截屏2023-04-21 14.42.40.png
截屏2023-04-21 14.42.53.png
对于5.0及以上版本之后,可以设置android:statusBarColor和android:navigationBarColor置为完全透明,无阴影。
-
第三步就是处理布局重叠问题了,完成上面的设置后,你会发现UI重叠,这个时候需要:
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) view.updatePadding(statusInsets.left, statusInsets.top, statusInsets.right, statusInsets.bottom) //返回windowInsets而不是WindowInsetsCompat.CONSUMED,为了继续往下传递insets //WindowInsetsCompat.CONSUMED windowInsets }
给布局设置上下的偏移以达到不被系统栏遮挡,
注意,如果内部的子View处理了insets,则可能会和这里的跟布局的处理重复从而造成一些问题,通常子View不需要再额外处理,除非有特殊需求。
比如,BottomNavigationView设置了OnApplyWindowInsetsListener,其内部有了对于insets左右和底部padding的处理,因此它会自动适配导航栏的高度,防止遮挡,这时如果跟布局也增加了对于insets的margin则会多出一块偏移,所以这种情况下跟布局的处理返回就要是WindowInsetsCompat.CONSUMED,这样子View(BottomNavigationView)就不会收到回调了。通常,没有额外处理的话,只需要跟布局添加OnApplyWindowInsetsListener处理insets偏移,然后返回WindowInsetsCompat.CONSUMED即可,当然也可以换种方式,即每个页面的子View单独添加监听处理,这样就很繁琐也不好维护。
设置android:fitsSystemWindows="true"也能实现避免遮挡,但这种方式需要单独页面配置。
-
对于刘海屏(DisplayCutout)来说,9.0及以上的版本才会兼容,对于该版本以下存在的情况来说则需要根据具体厂商的api进行手动获取高度并进行适当偏移。
-
下面我们从源码角度来分析WindowInsets的原理。
-
WindowCompat.setDecorFitsSystemWindows(window, false)
这个方法让应用内容可以延伸到系统栏下面去,我们来看它是怎么实现的:
public static void setDecorFitsSystemWindows(@NonNull Window window, final boolean decorFitsSystemWindows) { if (Build.VERSION.SDK_INT >= 30) { Api30Impl.setDecorFitsSystemWindows(window, decorFitsSystemWindows); } else if (Build.VERSION.SDK_INT >= 16) { Api16Impl.setDecorFitsSystemWindows(window, decorFitsSystemWindows); } }
这里有版本适配处理,低于16(4.1)没有沉浸式。
对于4.1到11之间的版本:
static void setDecorFitsSystemWindows(@NonNull Window window, final boolean decorFitsSystemWindows) { final int decorFitsFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; final View decorView = window.getDecorView(); final int sysUiVis = decorView.getSystemUiVisibility(); decorView.setSystemUiVisibility(decorFitsSystemWindows ? sysUiVis & ~decorFitsFlags : sysUiVis | decorFitsFlags); }
通过设置SystemUiVisibility来实现沉浸式 :
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE:表示systembar显示隐藏时都不会影响应用布局,否则应用布局可能会随之变化。
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:使应用内容延伸到系统栏下面。
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:隐藏导航栏,但实际上配置这个在5.0上并不会隐藏导航栏,这个标志只是表示需要延伸到系统栏下面而已,下面源码分析会看到,而使用SYSTEM_UI_FLAG_HIDE_NAVIGATION则可以隐藏导航栏。
对于11版本之后的,会调用PhoneWindow的setDecorFitsSystemWindows方法:
@Override public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) { mDecorFitsSystemWindows = decorFitsSystemWindows; applyDecorFitsSystemWindows(); } private void applyDecorFitsSystemWindows() { ViewRootImpl impl = getViewRootImplOrNull(); if (impl != null) { impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows ? sDefaultContentInsetsApplier : null); } }
沉浸式的时候mDecorFitsSystemWindows会被赋值为false,ViewRootImpl设置的OnContentApplyWindowInsetsListener为null:
public void setOnContentApplyWindowInsetsListener(OnContentApplyWindowInsetsListener listener) { mAttachInfo.mContentOnApplyWindowInsetsListener = listener; if (!mFirst) { requestFitSystemWindows(); } }
requestFitSystemWindows中会调用scheduleTraversals方法进行布局渲染流程。
为什么这里要判断mFirst为false呢?因为ViewRootImpl创建的时候mFirst是true,performTraversals中有:
if (mFirst) { ... dispatchApplyInsets(host); }
因此,在第一次创建渲染的时候自然会调用dispatchApplyInsets方法,此时不需要重复再去渲染。
-
dispatchApplyInsets
此时重点来到了dispatchApplyInsets方法:
public void dispatchApplyInsets(View host) { ... WindowInsets insets = getWindowInsets(true /* forceConstruct */); ... host.dispatchApplyWindowInsets(insets); ... }
insets从getWindowInsets中获取,其实就是生成一个新的WindowInsets对象,此时的偏移量还没设置。host是mView,mView就是DecorView,DecorView并没有实现这个方法,View中的dispatchApplyWindowInsets方法如下:
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { try { mPrivateFlags3 |= PFLAG3_APPLYING_INSETS; if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) { return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets); } else { return onApplyWindowInsets(insets); } } finally { mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS; } }
如果设置了OnApplyWindowInsetsListener则会执行这个回调,否则会调用实现的onApplyWindowInsets方法,DecorView中就实现了这个方法:
@Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { ... insets = updateColorViews(insets, true /* animate */); ... return insets; }
在updateColorViews方法中:
WindowInsets updateColorViews(WindowInsets insets, boolean animate) { WindowManager.LayoutParams attrs = mWindow.getAttributes(); int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); final WindowInsetsController controller = getWindowInsetsController(); // 和其他floating类型的window不同,IME是一种特殊的floating类型的window,也需要适配insets final boolean isImeWindow = mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD; //只有非floating类型的window和IME才需要适配insets //floating窗口表示的类似于windows系统上的窗口,mDecorCaptionView持有最小化、关闭等操作按钮 if (!mWindow.mIsFloating || isImeWindow) { ... final ViewRootImpl viewRoot = getViewRootImpl(); final @Appearance int appearance = viewRoot != null ? viewRoot.mWindowAttributes.insetsFlags.appearance : controller.getSystemBarsAppearance(); if (insets != null) { ... //获取不随bar的显示隐藏而变化的区域 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( WindowInsets.Type.systemBars()); //系统栏、刘海屏和稳定显示区域取交集 final Insets systemInsets = Insets.min(insets.getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()), stableBarInsets); //记录系统栏的偏移量,比如top是状态栏和刘海屏中较大的那一个 mLastTopInset = systemInsets.top; mLastBottomInset = systemInsets.bottom; mLastRightInset = systemInsets.right; mLastLeftInset = systemInsets.left; ... } //修改导航栏颜色 boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset); boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset); int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset); updateColorViewInt(mNavigationColorViewState, calculateNavigationBarColor(appearance), mWindow.mNavigationBarDividerColor, navBarSize, navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge, 0 /* sideInset */, animate && !disallowAnimate, mForceWindowDrawsBarBackgrounds, controller); boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground; ... boolean statusBarNeedsRightInset = navBarToRightEdge && mNavigationColorViewState.present; boolean statusBarNeedsLeftInset = navBarToLeftEdge && mNavigationColorViewState.present; int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset : statusBarNeedsLeftInset ? mLastLeftInset : 0; int statusBarColor = calculateStatusBarColor(appearance); updateColorViewInt(mStatusColorViewState, statusBarColor, 0, mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset, animate && !disallowAnimate, mForceWindowDrawsBarBackgrounds, controller); ... } //是否隐藏导航栏 boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 || !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR)); //这个是我们前面设置的false boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows; boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && decorFitsSystemWindows && !hideNavigation) || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); boolean consumingNavBar = ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && decorFitsSystemWindows && !hideNavigation) || forceConsumingNavBar; // 全凭可以通过sysUiVisibility、attrs和WindowInsetsController三个地方决定 boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0 || (attrs.flags & FLAG_FULLSCREEN) != 0 || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR)); boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && decorFitsSystemWindows && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsBarBackgrounds && mLastTopInset != 0 || (mLastShouldAlwaysConsumeSystemBars && fullscreen); //这里的consumedXxx表示上一级是否消耗掉了insets,如果消耗掉了,则下面的内容布局就不再消耗了,即不会延伸到系统栏下的区域 int consumedTop = consumingStatusBar ? mLastTopInset : 0; int consumedRight = consumingNavBar ? mLastRightInset : 0; int consumedBottom = consumingNavBar ? mLastBottomInset : 0; int consumedLeft = consumingNavBar ? mLastLeftInset : 0; if (mContentRoot != null && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams(); if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) { //mContentRoot是DecorView中应用内容显示区域的父容器,这里会给它设置margin来达到消耗insets的效果,比如状态栏若是26px,则应用内容会在状态栏下26px的位置开始绘制,这就是非沉浸式的;如果要沉浸式,则需要consumedXxx是0 lp.topMargin = consumedTop; lp.rightMargin = consumedRight; lp.bottomMargin = consumedBottom; lp.leftMargin = consumedLeft; mContentRoot.setLayoutParams(lp); ... } if (insets != null) { //这里会把系统栏的偏移量设置进去 insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom); } } ... return insets; }
下面我们来整理一下上面逻辑的意义。
-
首先获取系统栏相对于屏幕边缘的四个边偏移。
-
其次判断是否应用沉浸式模式,判断的依据有很多,这里我们只考虑设置沉浸式方法中使用的相关标志的影响。
还记得我们调用WindowCompat.setDecorFitsSystemWindows时的版本分支吧,对于16~30之间的版本,会通过设置setSystemUiVisibility为View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN的形式来使内容延伸到系统栏下面,对于这三个属性,SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION表示应用内容需要隐藏导航栏,因此配置这个属性会导致系统窗口不会consume掉insets偏移(其他属性你可以直接理解成符合条件,因为沉浸式设置时没有用到,比如attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS直接认为是true,相当于默认没配置),mLastShouldAlwaysConsumeSystemBars这个值是用来设置强制消费insets的,默认是false。因此,影响应用内容延伸到导航栏的因素就是setSystemUiVisibility设置为View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION或者SYSTEM_UI_FLAG_HIDE_NAVIGATION,或者给DecorView设置了WindowInsetController并且它的isRequestedVisible(ITYPE_NAVIGATION_BAR)返回false,SYSTEM_UI_FLAG_HIDE_NAVIGATION的意思是隐藏掉导航栏,自然应用区域随着需要延伸到最边缘,另外两个条件不会造成导航栏隐藏,但是表示需要延伸到导航栏。所以当设置了SYSTEM_UI_FLAG_HIDE_NAVIGATION、SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION或者添加的WindowInsetController的isRequestedVisible(ITYPE_NAVIGATION_BAR)返回false满足其一都会使consumingNavBar为false,若为false则mContentRoot(应用内容区域)就会较屏幕底部往上偏移0的距离,相当于不偏移,因此就会延伸到导航栏下面。
同理,对于statusBar来说也是一样,产生影响的是SYSTEM_UI_FLAG_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,一个表示隐藏状态栏,一个表示不隐藏但是需要延伸到状态栏下面,consumingStatusBar的判断没有单独根据fullscreen决定是因为默认把fullscreen当成需要延伸。mDecorFitsSystemWindows和上面的属性是或的关系,一样的作用,是针对不同版本的兼容,默认为true。
-
最后,设置mContentRoot的margin。
-
-
fitSystemWindow属性
我们知道,View设置fitSystemView属性后会自动适配insets的偏移,这是怎么做到的呢?
在ViewGroup中:
@Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { insets = super.dispatchApplyWindowInsets(insets); if (insets.isConsumed()) { return insets; } //下面为了简单,是我把另一个方法中的代码内联过来了 final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchApplyWindowInsets(insets); } return insets; }
可以看到,dispatchApplyWindowInsets会从DecorView往下分发,一直到最后一个View或者insets的isConsumed返回true:
public boolean isConsumed() { return mSystemWindowInsetsConsumed && mStableInsetsConsumed && mDisplayCutoutConsumed; }
WindowInstes类中有一个:
public static final @NonNull WindowInsets CONSUMED; static { CONSUMED = new WindowInsets((Rect) null, null, false, false, null); }
它符合isConsumed方法中为true的要求,如果不想继续往下分发insets则只需要在传递过程中返回一个WindowIsntes.CONSUMED即可。
@Deprecated protected boolean fitSystemWindows(Rect insets) { if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) { if (insets == null) { return false; } try { //添加这个属性,在下面流程中会再次调用到fitSystemWindows方法 mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS; //dispatchApplyWindowInsets方法中会添加PFLAG3_APPLYING_INSETS,这个属性会导致再次回到fitSystemWindows方法时会调用fitSystemWindowsInt方法 return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed(); } finally { mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS; } } else { return fitSystemWindowsInt(insets); } }
可以看到,这个方法其实被废弃了,推荐通过给View设置OnApplyWindowInsetsListener的方式消费insets。dispatchApplyWindowInsets中同样也是会调用OnApplyWindowInsetsListener或者onApplyWindowInsets方法。View的这个方法默认实现如下:
public WindowInsets onApplyWindowInsets(WindowInsets insets) { if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0 && (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) { return onApplyFrameworkOptionalFitSystemWindows(insets); } if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) { if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) { return insets.consumeSystemWindowInsets(); } } else { //如果不是因为设置了fitSystemWindows走到这的,也会调用fitSystemWindowsInt,因此你可以通过重写具体View的onApplyWindowInsets方法来消费insets if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) { return insets.consumeSystemWindowInsets(); } } return insets; }
前面设置了PFLAG3_FITTING_SYSTEM_WINDOWS,因此这里又走回到fitSystemWindows,此时有了PFLAG3_APPLYING_INSETS,因此会走到fitSystemWindowsInt方法:
private boolean fitSystemWindowsInt(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { Rect localInsets = sThreadLocal.get(); boolean res = computeFitSystemWindows(insets, localInsets); applyInsets(localInsets); return res; } return false; }
可以看到,需要设置fitSystemWindow属性,才会调用applyInsets方法,localInsets是上面传下来的系统insets:
private void applyInsets(Rect insets) { mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; mUserPaddingLeftInitial = insets.left; mUserPaddingRightInitial = insets.right; internalSetPadding(insets.left, insets.top, insets.right, insets.bottom); }
这里会通过设置View的padding为insets的偏移来达到解决和沉浸式系统栏的UI重叠问题的。
-
总结
-
通过底层API获取系统栏的占用偏移,其中top通过状态栏或者刘海屏等的高度确定的,取偏移最大的那个值,而左右和下偏移则是由导航栏的大小决定的。
-
DecorView中会先决定是否把所有屏幕区域都可交给子View处理,你可以根据不同版本设置不同属性来控制。
-
DecorView会给mContentParent(用户布局存放的地方)设置margin来给系统栏留出空间,如果margin为0,则用户应用区域就会延伸到系统栏区域了,也就是沉浸式了,这就是原理。
-
WindowInsets会携带者偏移数据从DecorView开始往下传递,默认子View的消费逻辑是给其设置padding来避免和系统栏的UI重叠冲突,但是这样的默认设置只能适用于最外层的根布局View,为啥?因为它会给View设置四个边的padding,比如给标题栏的View设置了fitSystemWindow属性后,你会发现它多了一个不需要的bottomPadding空间,如果导航栏不是充满左右的话,还会有左右padding产生。但是用于最外层这种方式还是无法完美适合某些场景,比如我需要看起来状态栏的背景色和标题栏颜色一致,但是标题栏颜色和根布局颜色不一致,这种情况下你会发现fitSystemWindow会使状态栏的背景色和根布局的一致而不是标题栏,这就会导致看起来好像不是沉浸式模式。
-
-
完美方案
通过源码分析和总结,我们发现,传统的fitSystemWindow并不能完全解决沉浸式的适配问题,对此,有两种方式可以解决。
-
重写View的fitSystemWindow来针对View的位置进行不同的insets设置,比如查到该View在布局的最顶部,则只需要应用上级传下来的insets的top,而botton设置为0,对于最底部的则相反,以此来针对不同的场景,腾讯的QUMUIWindowLayout就是用的这种方式。
-
官方推出了新的兼容API来实现:
ViewCompat.setOnApplyWindowInsetsListener(getBinder().root) { view, windowInsets -> //防止UI冲突 val systemInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) view.updatePadding( systemInsets.left, systemInsets.top, // max(windowInsets.displayCutout?.safeInsetTop ?: 0, systemInsets.top), systemInsets.right, systemInsets.bottom ) WindowInsetsCompat.CONSUMED }
这里是针对根布局的通用设置,因此返回WindowInsetsCompat.CONSUMED使其不再往下传递,避免有的子View有自己的处理而产生重复设置的问题。
上面是针对解决沉浸式模式下应用UI和系统栏的重叠问题的方法,他们都需要一个前提,就是我们上面分析的需要设置一些标志来开启沉浸式模式,调用下面这句代码可以完美实现:
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowCompat内部做了兼容处理。
-