自定义View系统总结之measure过程

本系列总结主要参考GcsSloop自定义View系列以及《Android开发艺术探索》中的相应章节内容,仅作为个人笔记使用。

  ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是ViewRoot来完成。当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。View的绘制流程从ViewRoot的performaTraversals方法开始,经过measure、layout、draw三个过程将一个View绘制出来。performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw流程。

  DecorView作为顶级VIew,一般它内部包含一个竖直方法的LinearLayout,这个LinearLayout里面有上下两个部分,上面是标题栏,下面是内容栏。Activity中setContentView所设置的布局文件是被加到内容栏中。用如下方式得到content,通过content.getChildAt(0)得到我们设置的View。

ViewGroup content = (ViewGroup) findViewById(android.R.id.content);

 

自定义View绘制流程函数调用链

  View的工作流程主要是指measure、layout、draw这三大流程,其中measure确定View的测量宽/高,layout确定View的最终宽/高和四个顶点的位置,draw负责将View绘制到屏幕上。

1.View的measure过程

View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,子类不能重写此方法,在View的measure方法中会调用View的onMeasure方法:

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

 setMeasureDimension会设置View宽/高的测量值,只需看getDefaultSize方法:

    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;
    }

AT_MOST和EXACTLY 模式,getDefaultSize返回的大小就是measureSpec中的specSize,即View测量后的大小。

UNSPECIFIED这种情况,一般用于系统内部的测量过程,View的大小为getSuggestedMinimumWidth和getSuggestedMinimumHeight这两个方法的返回值。

2.ViewGroup的measure过程

对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个方法。ViewGroup是一个抽象类,没有重写onMeasure方法,但是提供了一个measureChildren方法。

    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);
            }
        }
    }

 ViewGroup在measure时,会对每一个子元素进行measure,measureChild方法的实现:

    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);
    }

measureChild的思想就是取出子元素的LayoutParams,然后通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法来进行测量。ViewGroup其测量过程的onMeasure方法需要各个子类去具体实现,ViewGroup子类有不同的布局特性,测量细节也各不相同。

Tips

如在Activity已启动的时候做一任务来获取某个View的宽/高,在onCreate、onStart、onResume中均无法正确得到某个View的宽/高,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,如果View还没有测量完毕,那么获取的宽/高就是0.

解放方法:

1) 在onWindowFocusChanged方法中获取。View已经初始化完毕,宽/高已准备好,这时候获取宽/高是没问题的。当Activity的窗口得到焦点和失去焦点均会调用onWindowFocusChanged。

    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus) {
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    }

 2)view.post(runnable)。通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。

    protected void onStart() {
        super.onStart();
        view.post(new Runnable() {

            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

 3)使用ViewTreeObserver的众多回调可以完成这个功能。

 

posted @ 2018-12-15 17:11  kyun  阅读(180)  评论(0编辑  收藏  举报