美文网首页
android视图绘制

android视图绘制

作者: leoryzhu | 来源:发表于2019-04-11 11:56 被阅读0次

视图显示在屏幕上要经过三个过程,分别是measure、layout、draw,下面针对三个过程从源码角度解剖视图绘制的过程

一. onMeasure()

measure ,顾名思义,就是测量的意思,而onMeasure()就是测量视图的大小。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法
ViewRoot实现类ViewRootImpl关键代码如下

...
if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
...

View 的measure()方法是final修饰,不能被继承,内部调用onMeasure(),measure()关键代码

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
  onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}

View的onMeasure()有一个默认的实现

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

其中setMeasuredDimension()方法是设置视图的大小,getDefaultSize()是通过MeasureSpec获取宽高大小的默认方法,可以看看它的具体实现,

 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

我们自定义View时如果想怎么测量控件的大小时,直接覆盖实现onMeasure()方法,测量宽高大小,最后调用setMeasuredDimension()方法设置视图大小。
测量布局,ViewGroup中没有实现具体的OnMeasure()方法,因为不同的布局的测量方法不一样,所以要在ViewGroup子类中实现OnMeasure()方法。
一个布局ViewGroup一般包含多个子View,这时ViewGroup提供了measureChildren()来测量子View,代码如下

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

调用了measureChild来测量子View

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

最终调用了子View的measure()方法测量子View。
所以自定义布局如果想自己测量布局的话,首先测量子View,再用setMeasuredDimension()方法测量布局本身

二.onLayout()

measure测量完成后,接下来就是layout,顾名思义就是给视图布局,也就是确定视图的位置。ViewRoot的performTraversals()方法在执行完measure()方法后,就会调用layout方法,关键代码如下

...
  host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...

也就是调用了View的layout()方法,下面来看看layout()方法关键代码,参数l,t,r,b分别代表上,下,左,右坐标

 public void layout(int l, int t, int r, int b) {
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            ...
        }
}

layout里面主要调用了onLayout()方法,进入看看

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

是一个空实现,是的,因为onLayout()过程是为了确定视图在布局中所在的位置,这个操作应该要布局视图来完成,即父布局决定了子视图的位置,既然这样,看看ViewGroup里面的onLayout()方法

 @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

是一个抽象方法,意味着所有的ViewGroup子类都要实现onLayout()方法

下面看看ViewGroup的子类FrameLayout实现的onLayout()方法

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

layoutChildren()关键代码

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        ...
 
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

代码实现的步骤是先算出上下左右的坐标,再通过child.layout()确认子View的位置。

三. onDraw()

layout确定视图位置之后,就要draw啦,顾名思义,就是真正的绘制啦,ViewRoot 执行要layout()之后,接着创建出一个Canvas对象,然后调用draw()方法要执行具体的绘制工作。draw具体的关键代码如下,第二步和第五步很少用到,这里省略了。

public void draw(Canvas canvas) {
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

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

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



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

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

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }


     

通过上面的注释大概也看懂什么意思
step 1 drawBackground()是绘制背景
step 3 onDraw()是绘制内容,点进去看

  protected void onDraw(Canvas canvas) {
    }

是一个空方法,具体的控件有具体的绘制方法
step 4 dispatchDraw()是绘制子视图,这个方法也是空实现,ViewGroup才会用到
step 6 绘制前景,滚动条

一般情况下我们只要实现onDraw()方法来实现我们绘制的内容

相关文章

网友评论

      本文标题:android视图绘制

      本文链接:https://www.haomeiwen.com/subject/dhoniqtx.html