View的绘制流程二(视图的展示)

文章目录

      • ViewRoot和DecorView
      • MeasureSpce
      • MeasureSpce和LayouParams
        • 普通View的MeasureSpce确定,父容器的MeasureSpce和自身的LayoutParams的关系如下图:
      • ViewGroup的measure过程
      • View的measure过程
      • Layout过程
      • Draw过程
      • getMeasureWidth和getWidth的区别
      • 自定义View重写onMeasure()后布局中wrap_content失效
      • 在activity中获取View的宽高
      • invalidate、postInvalidate与requestLayout三个方法的区别:

ViewRoot和DecorView

整个界面的绘制是从ViewRoot开始的,ViewRoot的对应类是ViewRootlmp类,ViewRoot是连接DecorView和WindowManager的纽带。
界面绘制首先会调用其performTraversals()函数,然后它需要经过三个流程才能够将一个View绘制出来,分别经过measure,layout,draw三个流程,measure是测量View视图的宽高,layout是设定View的位置,draw是最终将View绘制在屏幕上;

其具体逻辑关系如下图:

View的绘制流程二(视图的展示)_第1张图片

其中,performMeasure,performLayout,performDraw分别完成了顶级View的measure,layout,和draw的过程;
在performMeasure中又会调用measure,在measure中又会调用onMeasure函数,在onMeasure中,父容器会对所有的子元素进行measure过程,这个时候measure流程就就从父容器传递到了子容器中,这就完成了一个measure流程,在子容器中又会重复父容器的measure流程,反复循环直至遍历完整个View树。
而performLayout,performDraw其传递的原理和performMeasure是一样的。


MeasureSpce

MeasureSpce需要单独进行说明一下,因为MeasureSpce很大程度上决定了一个View的尺寸大小,之所以说是很大程度是因为,View的尺寸大小除了受到MeasureSpce的影响之外,还受到父容器MeasureSpce和该View的layoutParams的影响,关于layoutParams系统会将layoutParams转换成对应的MeasureSpce,再根据这个MeasureSpce来对该View进行测量.

MeasureSpce 是一个32位的int类型数值,前2位是SpceMode,后30位是SpceSize,SpceMode表示的是测量模式,SpceMode表示的是某种测量模式下规格的大小.

SpceMode的类型有三种,分别如下:

  • UNSPECIFIED:
    父容器不对View有任何影响,View要多大给多大,通常用于系统内部

  • EXACTLY:
    父容器已经检测出了View的精确大小,而View大小具体的数值就是SpceSize的值,它对应LayoutParams中的match_parent和具体数值这两种

  • AT_MOST:
    父容器指定了一个可用大小即SpceSize,View的大小不能大于这个值,具体什么值需要看View的具体实现,它对应LayoutParams中的wrap_content


MeasureSpce和LayouParams

顶级View(DecorView)的MeasureSpce是取决于本身的LayoutParams.

普通View的MeasureSpce是取决于父容器的MeasureSpce和自身的LayoutParams.MeasureSpce一旦确定,就可以在onMeasure确定View的测量宽高.

普通View的MeasureSpce确定,父容器的MeasureSpce和自身的LayoutParams的关系如下图:

View的绘制流程二(视图的展示)_第2张图片

  • 当View的宽高采用的是固定数值的时候,不论父容器是精确模式还是最大模式,View的MeasureSpce都是精确模式,其大小数值是LayoutParams中的数值
  • 当View的宽高采用的是match_parent,当父容器是精确模式的时候,View的MeasureSpce是精确模式,其大小是父容器的剩余空间,当父容器是最大模式的时候,View的MeasureSpce是精确模式是最大模式,其大小是父容器的剩余空间
  • 当View的宽高采用的是wrap_content,不论父容器的MeasureSpce是什么模式,View的MeasureSpce模式都是最大模式,且大小不超过父容器的剩余空间
  • 以上并未介绍UNSPECIFIED,是因为通常用于系统内部多次measure的情形,因此没有太关注该模式.

ViewGroup的measure过程

对于ViewGroup来说,它除了完成自身的measure流程之外,还会去遍历子所有的子元素,执行子元素的measure方法,ViewGroup与View与之不同的一点是,ViewGroup是一个抽象类,没有onMeasure()函数,但ViewGroup有一个叫measureChildren的函数

protected void measureChildren(int widthMeasureSpec,int heightMeasureSpec){
final int size=mchi1drencount;
final view[] children =mChildren;

for (inti=0;i

在measureChildren函数中完成了对每一个子元素的遍历工作,并且调用了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()中,首先获取到了当前View的LayoutParams,通过形参获取到了父类的MeasureSpec,然后通过getChildMeasureSpec()函数,传入该View的LayoutParams和父元素的LayoutParams创建了该View的MeasureSpec.


View的measure过程

View的measure是一个被final修饰的函数,所以不能重新,不过在measure函数中还调用了onMeasure()函数,我们在复写时,可以复写onMeasure()函数.
onMeasure()函数代码如下:

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
setMeasuredDimension(getDefaultsize(getSuggestedMinimumwidth(),widthMeasureSpec),getDefaultsize(getSuggestedMinimumHeight(),heightMeasureSpec));
}

在onMeasure()函数中,通过调用setMeasureDimension()函数完成了对于View的测量,其四个参数分别是:width的默认值,widthMeasureSpec,height的默认值,heightMeasureSpec.

从这一段函数我们可以看出,测量的View的宽高值是存储在MeasureSpec中的SpecSize中,而关于MeasureSpec的确定在父容器中完成的.

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

从代码中看到,当测量模式是精确模式或者是最大模式的时候返回值就是MeasureSpec中的SpecSize.
当测量模式是系统内部用到的UNSPECIFIED模式返回值为getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()

getSuggestedMinimumWidth()和getSuggestedMinimumHeight()代码如下:

protected int getSuggestedMinimumwidth()(
return(mBackground==nul1)? mdinwidth:max(mMinWidth,mBackground.
getMinimumwidth());
}
protected int getsuggestedMinimumHeight(){
return(mBackground==nul1)?mMinleight:max(mMinHeight,mBackground.
getMinimumHeight());
}

//所调用到的getMinimumwidth()如下

public int getMinimumwldth()(
final int intrinaicwidth =getIntrinsicwidth();
return intrinsicwidth>0? intrinsicNidth:0;
}

由上可以看到,其实现逻辑是,首先判断是否设定有背景图片,当没有的时候就获取android:minWidth属性所设定的值,该值可以为0,当设置了背景图片就在android:minWidth和图片尺寸中选最大值进行返回


Layout过程

Layout的作用就是ViewGroup用来确定子元素的位置,在onLayou()函数中会遍历所有的子元素并调用其layout()函数,在该layout()函数中又会调用onLayout()函数.

  • 大致流程:从顶级View开始依次调用layout(),其中子View的layout()会调用setFrame()来设定自己的四个顶点(mLeft、mRight、mTop、mBottom),接着调用onLayout()来确定其坐标,注意该方法是空方法,因为不同的ViewGroup对其子View的布局是不相同的。

View的绘制流程二(视图的展示)_第3张图片


Draw过程

Draw的绘制过程也比较简答,其绘制流程遵循如下步骤:

  1. 绘制背景 background.draw(canvas) : 调用drawBackground方法绘制View的背景(内部调用Drawable的draw方法)
  2. 绘制自己(onDraw):调用onDraw方法绘制View的具体内容
  3. 绘制child(dispathDraw) : 调用dispatchDraw方法绘制子View
  4. 绘制装饰(onDrawForeground) : 调用onDrawForeground方法绘制装饰(例如,滚动条)

getMeasureWidth和getWidth的区别

getWidth代码如下:

public final int getWindth(){
    return mRight - mLeft;
}

getWindth计算的返回值和测量值是一样的,所以,在View的默认实现下,测量的宽高和最终的宽高是一样的,只不过赋值的时间不同,测量宽高的赋值是在measure流程,而最终宽高的赋值是在layout流程,相比测量赋值的时间稍微晚一些.


自定义View重写onMeasure()后布局中wrap_content失效

View的绘制流程二(视图的展示)_第4张图片

在getDefaultSize()函数中有看到,精确模式和最大模式最受选择都是MeasureSpec中的SpecSize,另在上图给出的图中,我们可以看到,wrap_content属性占有的空间是父容器剩余空间的大小,这就导致,在这种情况下使用match_parent和wrap_content展现效果是一样的,所以事wrap_content生效,我们需要为其设定最小固定值
示例代码如下:

protected void onMeasure(int widthMeasureSpec,int heightMeasurespec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSpecsize=MeasureSpec.getsize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpecsize=MeasureSpec.getSize (heightMeasureSpec);
if (widthspecMode =-MeasureSpec.AT_MoST && heightSpecMode ==
MeasureSpec.AT_MoST){
setMeasuredDimension(mWidth,mHeight);
)else if (widthspecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecsize);
)else if (heightSpecMode==MeasureSpec.AT_MoST)
setMeasuredDimension(widthSpecsize,mHeight);}
}
}

在activity中获取View的宽高

在activity中获取某一View的宽高,需要注意的一点是,View的measure流程和activity的生命周期是不同步的,也就是说不能够保证,在activity的onCreate,onStart,onResume中任一周期时,某个View的measure流程完成.

能够在activity中获取View宽高的方法有如下四种:

1. onWindowFocusChange:
onWindowFocusChange意思就是所有的View都初始化完成了可以调用了,其特性是activity在每次失去焦点和获得焦点的时候都会调用该方法,示例代码如下:

public void onWindwFcusChange(boolean hasFocus){
    if(hasFocus){
        int width = view.getMeasureWidth();
        int height = view.getMeasureHeight();
    }
}

2. view.post(runnable): 在runnable中包含有获取view的宽高,通过post将该runnable投递到队列尾部,然后等待LOOP调用,当调用到该runnable时View的初始化已经完成,所有可以直接获取到宽高.示例代码如下:

proceted void onStart(){
    view.post(new Runnable(){
    @override
    public void run(){
        int width = view.getMeasureWidth();
        int height = view.getMeasureHeight();
    }
})
}

3. ViewTressObserver
可以使用ViewTressObserver中的onGlobalLayoutListener,在View树的状态发生改变或者View树内部View的可见性发生改变,则会调用onGlobalLyout()函数,所有,也可以使用该方式获取View的宽高,示例代码如下:

ViewTressObserver observer =  view.getViewTreeeObserver();
observer.addGlobalLayoutListener(new GloblLayoutListener(){
    @suppressWarnings("deprecation")
    @override
    public void onGloablLayout(){
        view.getViewTreeeObserver().removeGlobalLayoutListener(this);
        int width = view.getMeasureWidth();
        int height = view.getMeasureHeight();
    }
})

4. 通过view.measure(int widthMeasureSpec,int heightMeasureSpec)函数手动设置

通过该方式需要分别按几种方式讨论

  • match_parent:这种方式,其View的大小是父容器的剩余空间,而我们当前是无法获取到父容器的剩余空间,所以无法测量出View的宽高
  • 具体数值:
widthMeasureSpec  = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
  • wrap_content:
    这种方式我们可以设定其最大值,其二进制方式最大是30个1,那么就是2^30-1,也就是(1 << 30)-1;在最大化模式下,我们使用最大化值去构建,是合理的
widthMeasureSpec  = MeasureSpec.makeMeasureSpec((1 << 30)-1),MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30)-1,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);

invalidate、postInvalidate与requestLayout三个方法的区别:

  1. invalidate只会调onDraw方法且必须在UI线程中调用
  2. postInvalidate只会调onDraw方法,在工作线程刷新view
  3. requestLayout会调onMeasure、onLayout和onDraw(特定条件下)方法

SurfaceView和View的区别?

  • SurfaceView是从View基类中派生出来的显示类,他和View的区别有:
    View需要在UI线程对画面进行刷新,而SurfaceView可在子线程进行页面的刷新
  • View适用于主动更新的情况,而SurfaceView适用于被动更新,如频繁刷新,这是因为如果使用View频繁刷新会阻塞主线程,导致界面卡顿
  • SurfaceView在底层已实现双缓冲机制,而View没有,因此SurfaceView更适用于需要频繁刷新、刷新时数据处理量很大的页面

SurfaceView用法

你可能感兴趣的:(View)