WindowInsets 在View下的的分发(二)
来源:互联网 发布:小米智能家庭软件 编辑:程序博客网 时间:2024/05/29 16:26
绪论
在上一篇中,大概说明了下WindowInsets的概念和分发逻辑,然而在部分情况下,我们会发现即便设置了fitSSystemWindows = true 也并没有生效;而且从上文已知的情况可以看出,即便消费WindowInsets似乎也只是在消费SystemWindowInsets,其它的Insets似乎并没有被消耗。这一篇将解决这两个问题。
WindowInsets 在View下的的分发(二)
mWindowDecorInsets和mStableInsets的消耗
fitSSystemWindows = true 生效的首要条件
这个问题的答案,我们可以在ViewRootImpl和Decoreview这两个类中找到答案
在ViewRootImpl的源码中,我们可以发现这样一段代码
private void performTraversals() { ... ... dispatchApplyInsets(host); ... ...}void dispatchApplyInsets(View host) { host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));}WindowInsets getWindowInsets(boolean forceConstruct) { if (mLastWindowInsets == null || forceConstruct) { mDispatchContentInsets.set(mAttachInfo.mContentInsets); mDispatchStableInsets.set(mAttachInfo.mStableInsets); Rect contentInsets = mDispatchContentInsets; Rect stableInsets = mDispatchStableInsets; // For dispatch we preserve old logic, but for direct requests from Views we allow to // immediately use pending insets. if (!forceConstruct && (!mPendingContentInsets.equals(contentInsets) || !mPendingStableInsets.equals(stableInsets))) { contentInsets = mPendingContentInsets; stableInsets = mPendingStableInsets; } Rect outsets = mAttachInfo.mOutsets; if (outsets.left > 0 || outsets.top > 0 || outsets.right > 0 || outsets.bottom > 0) { contentInsets = new Rect(contentInsets.left + outsets.left, contentInsets.top + outsets.top, contentInsets.right + outsets.right, contentInsets.bottom + outsets.bottom); } mLastWindowInsets = new WindowInsets(contentInsets, null /* windowDecorInsets */, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), mAttachInfo.mAlwaysConsumeNavBar); } return mLastWindowInsets;}
从上述代码中,可以发现被dispatchapply的WindowInsets来源于getWindowInsets(…)。而在这个函数中,我们可以发现mWindowDecorInsets值为null,表明其从一开始就是消耗状态。
再看看DecoreView的代码
@Overridepublic WindowInsets onApplyWindowInsets(WindowInsets insets) { ... ... insets = updateColorViews(insets, true /* animate */); insets = updateStatusGuard(insets); insets = updateNavigationGuard(insets); if (getForeground() != null) { drawableChanged(); } return insets;}WindowInsets updateColorViews(WindowInsets insets, boolean animate) { WindowManager.LayoutParams attrs = mWindow.getAttributes(); int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); ... ... boolean consumingNavBar = (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 || mLastShouldAlwaysConsumeNavBar; // If we did not request fullscreen layout, but we still got it because of the // mForceWindowDrawsStatusBarBackground flag, also consume top inset. boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsStatusBarBackground && mLastTopInset != 0; 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) { lp.topMargin = consumedTop; lp.rightMargin = consumedRight; lp.bottomMargin = consumedBottom; lp.leftMargin = consumedLeft; mContentRoot.setLayoutParams(lp); if (insets == null) { // The insets have changed, but we are not currently in the process // of dispatching them. requestApplyInsets(); } } if (insets != null) { insets = insets.replaceSystemWindowInsets( insets.getSystemWindowInsetLeft() - consumedLeft, insets.getSystemWindowInsetTop() - consumedTop, insets.getSystemWindowInsetRight() - consumedRight, insets.getSystemWindowInsetBottom() - consumedBottom); } } if (insets != null) { insets = insets.consumeStableInsets(); } return insets;}
从上述代码中,我们可以发现在没设置 SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 和 SYSTEM_UI_FLAG_HIDE_NAVIGATION这两个属性时,会通过设置margin的方式消耗掉底部和左部mSystemWindowInsets的底部,而没设置 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 则会消费掉mSystemWindowInsets的顶部和右部。并且在insets不为空的情况下一定会消耗掉mStableInsets。这同时也是另外一个问题的答案,要想fitsSystemWindows起效,先得设置合适的sysUiVisibility属性。
部分特殊View的 WindowInsets分发逻辑
- DrawerLayout
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ... ... if (ViewCompat.getFitsSystemWindows(this)) { IMPL.configureApplyInsets(this); mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context); } ... ...}
WindowInsets的分发将通过IMPL.configureApplyInsets(this)实现,以android 版本大于20为例
public void configureApplyInsets(View drawerLayout) { DrawerLayoutCompatApi21.configureApplyInsets(drawerLayout);}
public static void configureApplyInsets(View drawerLayout) { if (drawerLayout instanceof DrawerLayoutImpl) { drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); }}
在该函数中设置了OnApplyWindowInsetsListener,并设置了View.SYSTEM_UI_FLAG_LAYOUT_STABLE和 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN两个属性,根据上文可以判断,Decoreview将不会消耗mSysWindowInsets的顶部,它会参与向下级的View分发,再看看InsetsListenr的实现。
static class InsetsListener implements View.OnApplyWindowInsetsListener { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { final DrawerLayoutImpl drawerLayout = (DrawerLayoutImpl) v; drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); return insets.consumeSystemWindowInsets(); }}
DrawerLayout将会消耗mSystemWindowInsets。
再看下setChildInsets实现,代码实现在DrawerLayout类中
@Overridepublic void setChildInsets(Object insets, boolean draw) { mLastInsets = insets; mDrawStatusBarBackground = draw; setWillNotDraw(!draw && getBackground() == null); requestLayout();}
可以看出mLastInsets将会参与mSystemWindowInsets的后续处理
在onMeasure(…) 函数中可以发现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (applyInsets) { final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection); if (ViewCompat.getFitsSystemWindows(child)) { IMPL.dispatchChildInsets(child, mLastInsets, cgrav); } else { IMPL.applyMarginInsets(lp, mLastInsets, cgrav); } }}//DrawerLayoutCompatApi21.javapublic static void dispatchChildInsets(View child, Object insets, int gravity) { WindowInsets wi = (WindowInsets) insets; if (gravity == Gravity.LEFT) { wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); } else if (gravity == Gravity.RIGHT) { wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); } child.dispatchApplyWindowInsets(wi);}public static void applyMarginInsets(ViewGroup.MarginLayoutParams lp, Object insets,int gravity) { WindowInsets wi = (WindowInsets) insets; if (gravity == Gravity.LEFT) { wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); } else if (gravity == Gravity.RIGHT) { wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); } lp.leftMargin = wi.getSystemWindowInsetLeft(); lp.topMargin = wi.getSystemWindowInsetTop(); lp.rightMargin = wi.getSystemWindowInsetRight(); lp.bottomMargin = wi.getSystemWindowInsetBottom();}
如果child中设置fitsSystemWindow = true 属性,则会执行子view的dispatch, 否则会重新设置View的margin属性
- CoordinatorLayout
与DrawerLayout类似,通过设置OnApplyWindowInsetsListener来改变它的dispatchApply逻辑,与DrawerLayout最大的区别在于它对子view的分发是通过Behavior实现的。
- CollapsingToolbarLayout
它也是通过设置OnApplyWindowInsetsListener来实现的, 并且当它的VieParent是AppBarLayout时,它的fitsSystemWindow属性与其ViewParent一致
@Overrideprotected void onAttachedToWindow() { super.onAttachedToWindow(); // Add an OnOffsetChangedListener if possible final ViewParent parent = getParent(); if (parent instanceof AppBarLayout) { // Copy over from the ABL whether we should fit system windows ViewCompat.setFitsSystemWindows(this, ViewCompat.getFitsSystemWindows((View) parent)); if (mOnOffsetChangedListener == null) { mOnOffsetChangedListener = new OffsetUpdateListener(); } ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener); // We're attached, so lets request an inset dispatch ViewCompat.requestApplyInsets(this); }}WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) { WindowInsetsCompat newInsets = null; if (ViewCompat.getFitsSystemWindows(this)) { // If we're set to fit system windows, keep the insets newInsets = insets; } // If our insets have changed, keep them and invalidate the scroll ranges... if (!objectEquals(mLastInsets, newInsets)) { mLastInsets = newInsets; requestLayout(); } // Consume the insets. This is done so that child views with fitSystemWindows=true do not // get the default padding functionality from View return insets.consumeSystemWindowInsets();}
它会消耗windowinsets并且让子view不再消耗WindowInsets了。
- WindowInsets 在View下的的分发(二)
- WindowInsets 在View下的的分发(一)
- View的分发机制(二)
- View的事件分发二
- View及ViewGroup的事件分发及传递(二)
- View及ViewGroup的事件分发及传递(二)
- View及ViewGroup的事件分发及传递(二)
- View及ViewGroup的事件分发及传递(二)
- Android自定义View的事件分发机制(二)
- View的事件分发机制二--事件分发规则
- View的事件体系(下)(事件分发,滑动冲突)
- Android——View的事件体系(二)View的事件分发机制
- 自定义View(二)--表层浅析View的事件分发机制和滑动冲突
- View的事件分发
- View的事件分发
- view的分发事件
- View的事件分发
- View的事件分发
- CentOS 7 安装 MySQL
- Android Launcher7.0首次数据加载逻辑
- 用Python和Pygame写游戏-从入门到精通(21) 一个重力模拟金属球碰撞的例程
- JAVA多线程实现的四种方式
- 【线段树 面积并 扫描线】HDU
- WindowInsets 在View下的的分发(二)
- 使用@Autowired 报空指针解决方案
- ArrayList和LinkedList的区别
- java8 的 map 排序
- 异常机制的初解
- 倒入别人项目出现红j
- 移动开发滑动隐藏导航条功能
- Webpack 入门教程
- TensorFlow学习笔记(UTF-8 问题解决 UnicodeDecodeError- 'utf-8' codec can't decode byte 0xff in position 0- in