11月更文挑战|Android基础-View的layout和draw过程

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

layout过程

Viewmeasure过程之后就是Viewlayout过程。相对而言layout过程比起measure过程会简单一点。layout过程是从ViewRootImplpergormLayout()方法开始,performLayout()会调用layout()

public void layout(int l, int t, int r, int b) {

        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = 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(changed, l, t, r, b);
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            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;
    }
复制代码

layout方法实现流程,先通过setFrame方法设定View的四个顶点位置:mLeftmRightmTopmBottom。坐标确定好之后就能够得到View在父容器的位置。接着会调用onLayout方法,但在ViewonLayout没有实现,ViewGroup中同样也是,因此onLayout方法是在具体实现类中去实现的。可以通过具体实现类LinearLayout方法了解具体是怎么实现的。

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}
复制代码

layoutVertical方法中会遍历所有子视图调用它们的setChildFrame方法来确定子视图位置。在这过程中childTop会逐渐增大,在后续添加的子视图会放置在靠下的位置,符合垂直方向排列布局属性。父容器在layout方法完成自身定位后还有执行onLayout方法去调用子视图的layout方法,然后子视图就能确定自己的位置。View树通过一层层的layout方法传递调用完成整课树位置信息定位。

draw过程

Viewdraw步骤过程主要分以下几点:

  1. 绘制背景(有必要时保存画布层)
  2. 绘制View的内容
  3. 绘制子View(有必要时保存画布层)
  4. 绘制边框等装饰类额外属性
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed
int saveCount;

drawBackground(canvas);

....

// Step 2, save the canvas' layers

    // Step 3, draw the content
    onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers

// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);

// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);

    }

}
复制代码

略过方法drawBackground(canvas)重点关注了解onDraw(canvas)dispatchDraw(canvas)的实现。在ViewViewGrouponDraw(canvas)方法没有任何实现是空的,dispatchDraw(canvas)有在ViewGroup中实现,具体实现在具体实现类当中。但大体上也能了解绘制上先绘制自身再去分发绘制去绘制子视图。因此绘制是通过方法dispatchDraw(canvas)进行传递一层层向子视图去做绘制的。

此外View还有一个方法setWillNotDraw,当View不需要绘制任何内容时可以标记为true,系统内部会做绘制优化。View默认是不启动这个优化功能,但ViewGroup会默认启动。对于继承ViewGroup的自定义View开发时就需要注意这点,当需要使用onDraw方法时需要去关闭will_not_draw标记。

猜你喜欢

转载自juejin.im/post/7032238904631623688