View的布局控件树阶段

这篇博客探讨了Android中View的布局过程,重点在performLayout()方法和透明区域的计算。在控件树布局期间,每个View的坐标通过layout()方法设置,并递归布局子View。对于含有SurfaceView的情况,会特别处理窗口的透明区域,通过Region类来计算。SurfaceView的行为与其他View相反,它在透明区域计算中增加自己的区域,可能导致之前设置的非透明区域被覆盖。SurfaceView的绘制不在onDraw()中,而是在SurfaceHolder.lockCanvas()中进行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.performLayout():执行控件树布局

2.mView.gatherTransparentRegion():在控件树布局完成后,如果控件树中存在SurfaceView,则需要计算窗口的透明区域

布局控件树:

调用performLayout(),这个方法内容比较简单,主要是一句:

//host为RootViewImpl的根View
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

这样就启动了这个控件树的布局流程。

layout()方法:

 public void layout(int l, int t, int r, int b) {//传进来的是相对于父控件的坐标
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
//... ...
//设置mLeft、mTop、mBottom、mRight四个成员
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//onLayout()是用于布局子控件的,所以如果不是ViewGroup则什么都不用做
            onLayout(changed, l, t, r, b);
//... ...

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
//回调OnlayoutChangeListener
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

//... ...
    }

1.设置该控件坐标

2.调用子控件的layout()方法设置子控件的坐标

3.回调该控件的OnLayoutChangeListener

计算窗口的透明区域:

这个主要是针对SurfaceView的,当控件树中存在SurfaceView才会调用ViewParent.requestTransparentRegion(),才会执行计算窗口透明区域的代码。

主要是Region这个类,这个Region代表的区域是透明的区域,先设置Region为整个窗口的,然后再让控件树中的每一个View都参与计算,即把自己区域从这个Region中删掉,但是SurfaceView不一样,SurfaceView做和普通View相反的操作,即把自己的区域和Region合并,所以在SurfaceView之前被遍历的View设置的非透明区域有可能会被SurfaceView设置的透明区域覆盖。

普通View的gatherTransparentRegion()的核心代码如下:

// The SKIP_DRAW flag IS NOT set, so this view draws. We need to
                // remove it from the transparent region.
                final int[] location = attachInfo.mTransparentLocation;
                getLocationInWindow(location);
                // When a view has Z value, then it will be better to leave some area below the view
                // for drawing shadow. The shadow outset is proportional to the Z value. Note that
                // the bottom part needs more offset than the left, top and right parts due to the
                // spot light effects.
                int shadowOffset = getZ() > 0 ? (int) getZ() : 0;
                region.op(location[0] - shadowOffset, location[1] - shadowOffset,
                        location[0] + mRight - mLeft + shadowOffset,
                        location[1] + mBottom - mTop + (shadowOffset * 3), Region.Op.DIFFERENCE);

SurfaceView的gatherTransparentRegion()如下:

int w = getWidth();
            int h = getHeight();
            if (w>0 && h>0) {
                getLocationInWindow(mLocation);
                // otherwise, punch a hole in the whole hierarchy
                int l = mLocation[0];
                int t = mLocation[1];
                region.op(l, t, l+w, t+h, Region.Op.UNION);
            }

View使用region和该view的区域做DIFFERENCE操作,即取region的补集

SurfaceView使用region和该SurfaceView的区域做UNION操作,即region和SurfaceView的区域的合集

SurfaceView参与控件树的测量和布局,只是不参与绘制,绘制不是在onDraw中,而是getSurfaceHolder().lockCanvas()获取Canvas向Surface绘制内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值