美文网首页
View的工作原理

View的工作原理

作者: 阿泽Leo | 来源:发表于2018-09-25 17:31 被阅读0次

本文不再讨论自定义View,推荐阅读扔物线的HenCoder的自定义View系列文章。

View的流程简述

View的绘制流程是从ViewRoot的performTraversals方法开始的,主要是:measure、layout、draw。大概流程如下图:

View的绘制流程.jpg
  • measure方法确定View的测量宽、高
  • layout方法确定View的最终宽高四个顶点的位置。
  • draw方法将View绘制到屏幕上。
  • ViewRoot的实现类是ViewRootImpl,是连接WindowManager和DecorView的纽带。
  • 从ViewRoot的performTraversals开始,经过performMeasure()performLayout()performDraw()完成顶级View(DecorView)的绘制。其中performMeasure()会调用measure和onMeasure,在onMeasure中对所有子元素调用measure和onMeasure,如此循环则完成measure过程,layout、draw同理。
  • 顶级View,即DecorView是一个FrameLayout。内部有一个竖直方向的LinearLayout,包含了titleBar和contentView。如下图:
DecorView.png
  • 我们的setContentView就是将自己的View放进了content里面。获取自己的View可以这样做:
ViewGroup viewGroup = findViewById(android.R.id.content);
View view = viewGroup.getChildAt(0);

View的具体工作流程

1. measure过程

作用:确定View的测量宽高

  • MeasureSpec:一个32位的int值。高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小)。

SpecMode分为UNSPECIFIED、AT_MOST、EXACTLY。

  • UNSPECIFIED 父容器不对View做任何限制,常用于系统内部。
  • EXACTLY 父容器指定View的大小为SpecSize指定的值。对应match_parent具体数值
  • AT_MOST 父容器指定最大尺寸SpecSize。对应wrap_content。
  • 最终View的MeasureSpec由View的LayoutParams和父容器的MeasureSpec决定,一旦确定后,在onMeasure就可以确定View的测量宽高。

引用一张网上的图片帮助理解:


MeasureSpec.png

其中parentLeftSize表示父容器剩下的可用大小,childSize为子View的指定大小。

1.1 View的measure过程

在View的measure方法中会去调用View的onMeasure方法:

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

setMeasuredDimension方法会设置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;
}

可以看出,如果直接继承View的自定义控件没有重写onMeasure,那么参数match_parent和wrap_content将为同一结果。处理这个问题的常用代码:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int withSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int withSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    //处理warp_content为自己指定默认值,其余为本身测量值
    if (withSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (withSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(withSpecSize, mHeight);
    }

}
1.2 ViewGroup的measure过程

ViewGroup是一个抽象类,其中没有重写View的onMeasure方法但是提供了measureChildren的方法来测量子类,具体的测量过程需要子类去实现。

在实际开发中,需要在Activity启动时获取某个控件的宽高,如果在onCreate、onStart中获取这个View的宽高,有可能不是正确的数值。在View测量完成前,获取到的数值都是0。

解决方法有4种:

  • Activity/View#onWindowFocusChanged
    当Activity获得焦点时,View已经初始化完毕了。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
}
  • view.post(runnable)
    通过post将runnable投递到消息队列尾部,等待Looper调用此runnable时View也已经初始化好了。
view.post(new Runnable() {
    @Override
    public void run() {
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
});
  • ViewTreeObserver
    这是View树的回调接口,当View树状态改变或者View可见性改变时,onGlobalLayout会被回调。
  • view.measure(widthMeasureSpec, heightMeasureSpec)
    手动去测量view的宽高。

2. layout过程

作用:确定View的四个顶点的位置和最终宽高。
layout方法首先确定ViewGroup的位置,然后在onLayout中对子View遍历来确定子View的位置。
具体流程:


ViewGroup的layout过程.jpg

3. draw过程

作用:将View绘制到屏幕上。
View的过程如下几步:

  • 绘制背景:drawBackground()
  • 绘制自己:onDraw()
  • 绘制children:dispatchDraw()
  • 绘制装饰:onDrawForeground()

通过查看View的源码可以清晰看到:

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
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

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

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

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

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

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

        // we're done...
        return;
    }
    ...
}

相关文章

  • View 的测量

    接着上篇 View 基础 来讲 View 的工作原理,View 的工作原理中最重要的就是测量、布局、绘制三大过程,...

  • 【Android】自定义ViewGroup

    关于View的工作原理、绘制流程等,在第4章 View的工作原理[https://www.jianshu.com/...

  • View 工作原理

    1、 ViewRoot 和 DecorView 介绍 ViewRoot 对应于 ViewRootImpl 类,它...

  • View工作原理

    参考书籍:Android开发艺术探索注:京东链接https://item.jd.com/11760209.html...

  • View工作原理

    View工作原理 首先先来说明一下要掌握的知识 View绘制工作整体流程 Measure Layout Draw ...

  • View工作原理

    1、起步分析 在Activity启动分析中 知道,Activity的创建是在ActivityThread.perf...

  • View工作原理

  • View工作原理

    view有三大工作流程:测量、布局、绘制,分别对应着方法mesure、layout、draw ViewRoot和D...

  • View工作原理

    ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,Vi...

  • View的工作原理

    ViewRoot对应于ViewRootImpl类,是连接Windowmanager和DecorView的纽带,Vi...

网友评论

      本文标题:View的工作原理

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