每日一问:简述 View 的绘制流程

Android 开发中常常须要用一些自定义 View 去知足产品和设计的脑洞,因此 View 的绘制流程相当重要。网上目前有很是多这方面的资料,但最好的方式仍是直接跟着源码进行解读,每日一问系列一直追求短平快,因此本文笔者尽可能精简。java

想必大多数 Android 开发都知道自定义 View 须要关注的几个方法:onMeasure()onLayout()onDraw(),这其实也是每一个 View 相当重要的绘制流程。git

基本绘制都是会从根视图 ViewRootperformTraversals() 方法开始,从上到下遍历整个视图树,每一个View控件负责绘制本身,而 ViewGroup 还须要负责通知本身的子 View 进行绘制操做。performTraversals() 的核心代码以下:github

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //执行测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //执行布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //执行绘制流程
    performDraw();
}
复制代码

measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) 复制代码

每一个 View 都有本身的大小,因此基本自定义 View 的时候都须要重写 onMeasure() 这个方法,以定制化咱们的 View 的宽高。**若是不重写这个方法,咱们一般会出现 wrap_contentmatch_parent 是同样的显示效果。**至于缘由,其实一探源码便知。json

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

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

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}		
复制代码

能够看到,View 默认是会使用 getDefaultSize() 方法进行设置宽高的,在 AT_MOSTEXACTLY 两种状况下都会直接使用测量规格里面的尺寸。在 UNSPECIFIED 模式下会直接取getSuggestedMinimumWidth() 的返回值。canvas

getSuggestedMinimumWidth() 会直接根据是否设置 backgroud 来进行计算,须要注意的是,直接设置 color 做为 backgroud 也会直接采用 minXXX 的值。布局

ViewGroup 中,并无去重写 ViewonMeasure() 方法,而这都须要它的子类根据本身的逻辑去实现,好比 LinearLayoutRelativeLayout 明显测量逻辑是不同的。不过,ViewGroup 却是提供了一个 measureChildren() 方法来依次遍历每一个子 View 对其进行测量。post

在通过 onMeasure() 操做后,getMeasureWidth()getMeasureHeight() 方法就能够拿到正确的返回值了。spa

因为 View 的 measure 过程和 Activity 的生命周期方法不是同步执行的,若是 View 尚未测量完毕,那么得到的宽/高就是 0。因此在 onCreate()onStart()onResume() 中均没法正确获得某个 View 的宽高信息。能够经过在 onWindowFocusChanged() 判断获取到焦点后进行获取,或者使用 view.post() 方式。.net

layout()

public void layout(int l, int t, int r, int b) 复制代码

咱们能够重写的 onLayout() 方法主要做用是肯定子 View 的显示位置,因为 View 已是最小的层级,因此咱们在自定义 View 的时候一般不须要管这个方法,而在自定义 ViewGroup 的时候就不得不注意这个方法了。设计

通过 onLayout() 流程后,咱们的 leftrighttopbottom 得以赋值,因此这时候能够经过 getWidth()getHeight() 方法来获取 View 的实际宽高了。

注意:在 View 的默认实现中,View 的测量宽/高和最终宽/高是相等的,只不过测量宽/高造成于 View 的 measure 过程,而最终宽/高造成于 View 的 layout 过程,即二者的赋值时机不一样,测量宽/高的赋值时机稍微早一些。在一些特殊的状况下则二者不相等:

draw()

public void draw(Canvas canvas) 复制代码

绘制的流程也就是经过调用 View 的 draw() 方法实现的。draw() 方法里的逻辑看起来更清晰,我就不贴源码了。通常是遵循下面几个步骤:

  • 绘制背景 – drawBackground()
  • 绘制本身 – onDraw()
  • 绘制孩子 – dispatchDraw()
  • 绘制装饰 – onDrawScrollbars()

因为不一样的控件都有本身不一样的绘制实现,因此V iew 的 onDraw() 方法确定是空方法。而 ViewGroup 因为须要照顾子 View 的绘制,因此确定在 dispatchDraw() 方法里遍历调用了child的 draw() 方法。

参考:

Android View的绘制流程

blog.csdn.net/yisizhu/art…

相关文章
相关标签/搜索