WindowInsets 在View下的的分发(二)

绪论

在上一篇中,大概说明了下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的代码

@Override
public 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 didn't 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're 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类中

@Override
public 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.java
public 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一致

@Override
protected 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了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容