Android View底层到底是怎么绘制的

Android绘制链图:

网上很多讲Android  view的绘制流程往往只讲到了Measure - Layout - Draw。

但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。

 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

而最终调用了initWindowDecorActionBar这个方法,我们看下这个方法里面都实现了什么

<span style="font-size: 16px;"> </span><span style="font-size:14px;"> private void initWindowDecorActionBar() {
        Window window = getWindow();
        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }</span>

根据人家给我们的注释,这段代码是创建一个actionbar,初始化这个view和actionbar。这里面有一段很重要的代码:

window.getDecorView();

正式这段代码告知系统可以从view的根节点开始绘制了,通过DecorView方法,decorview调用了performTraversals方法,我们来看下performTraversals源码:

<span style="font-size:14px;">private void performTraversals() {
    final View host = mView;
    ...
    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
    draw(fullRedrawNeeded);
} </span>

调用然后系统再调用Measure
- Layout - Draw
实现了View的绘制。

我们看一下完整的绘制流程,直接上一张图,或许更能说明这个意思:

到这里,系统会调用我们之前的比较熟悉的几个方法:Measure - Layout
- Draw

Measure


Measure过程是计算视图大小,View中视图measure过程相关的方法主要有三个

public final void measure(int widthMeasureSpec, int heightMeasureSpec)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  

measure调用onMeasure,onMeasure测量完成后setMeasureDimension,setMeasureDimension是final类型,view的子类不需要重写。

measure
源码:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {  

        // first clears the measured dimension flag
        mPrivateFlags &= ~MEASURED_DIMENSION_SET;  

        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
        }  

        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);  

        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }  

        mPrivateFlags |= LAYOUT_REQUIRED;
    }  

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
}  

我们看一下OnMearsure方法:

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

这个方法主要是实现setMeasuredDimension,这个方法是测量view的大小:

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

而对于这个measuredWidth和measuredHeight参数,系统却调了一个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;
    }

widthMeasureSpec和heightMeasureSpec决定了Mode和Size的值,widthMeasureSpec和heightMeasureSpec来自父视图,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了.

关于视图的measure过程可以阅读以下LinearLayout源码。

Layout

measure过程确定视图的大小,而layout过程确定视图的位置。

public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

 函数中参数l、t、r、b是指view的左、上、右、底的位置,通过这几个参数来确定view在Windows的位置。

在layout函数中,重载了一个空函数

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

这个需要子类去实现的。

比如Linearlayout:

 protected void onLayout(boolean changed, int l, int t, int r, int b) {
      if (mOrientation == VERTICAL) {
          layoutVertical();
      } else {
          layoutHorizontal();
      }
  } 

具体实现请自行看源码。

而在最后无论是layoutVertical还是layoutHorizontal都会掉一个setChildFrame方法来控制显示位置。

private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}  

从上面看出,layout也是一个自上而下的过程,先设置父视图位置,在循环子视图,父视图位置一定程度上决定了子视图位置。

Draw

draw过程调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,对于activity来说就是调用的PhoneWindow.DecorView。

*      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)

根据view源码的注释,

1,绘制背景

2,保存画布图层

3,调用了onDraw方法,子类中实现onDraw方法

4,使用的dispatchDraw方法

View或ViewGroup的子类不用再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。

有兴趣的可以看看onDraw的源码。

时间: 2024-09-15 01:13:20

Android View底层到底是怎么绘制的的相关文章

android View与线程绘制一个小球沿轨迹运动

问题描述 android View与线程绘制一个小球沿轨迹运动 实现绘制一个小球,并运用线程的sleep使小球沿着一个圆运动如何实现 解决方案 先以你小球的位置记录下x,y 然后确定你要小球遇到的圆形的半径,还有圆心位置x1,y1 写个hander,定时post, 然后在handler 处理messge的函数中,做重绘你这个小球view的坐标(x,y). 看你要多久移动多少角度.通过圆心x1, y1 还有当前的小球位置x,y,然后把要移动的角度带进去,算一下这次移动小球新的位置x,y.然后就调用

Android View绘制的三大流程

本文讲的是Android View绘制的三大流程,View的工作流程主要是指measure.layout.draw这三大流程,即测量.布局和绘制,其中measure确定View的测量宽高,layout根据测量的宽高确定View在其父View中的四个顶点的位置,而draw则将View绘制到屏幕上,这样通过ViewGroup的递归遍历,一个View树就展现在屏幕上了.说的简单,下面带大家一步一步从源码中分析: Android的View是树形结构的: 基本概念 在介绍View的三大流程之前,我们必须先

android View层的绘制流程

还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现. 前面<Android触摸屏事件派发机制详解与源码分析一(View篇)>文章的3-1小节说过Android中的任何一个布局.任何一个控件其实都

Android View 如何绘制

上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感"的外表.那么View又是如何进行绘制了? 要了解View如何绘制,就需要了解canvas(画布)是什么?paint(画笔)能够做什么. Ⅰ.canvas就是表示一块画布,你可以在上面画你所朝思暮想的东西.当我们重写onDraw方法的时候,就能够拿到一个Canvas对象,这个就是你的舞台,画你所思所

Android View如何绘制_Android

上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感"的外表.那么View又是如何进行绘制了? 要了解View如何绘制,就需要了解canvas(画布)是什么?paint(画笔)能够做什么. Ⅰ.canvas就是表示一块画布,你可以在上面画你所朝思暮想的东西.当我们重写onDraw方法的时候,就能够拿到一个Canvas对象,这个就是你的舞台,画你所思所

13问13答全面学习Android View绘制_Android

本文通过13问13答学习Android View绘制,供大家参考,具体内容如下 1.View的绘制流程分几步,从哪开始?哪个过程结束以后能看到view? 答:从ViewRoot的performTraversals开始,经过measure,layout,draw 三个流程.draw流程结束以后就可以在屏幕上看到view了.  2.view的测量宽高和实际宽高有区别吗? 答:基本上百分之99的情况下都是可以认为没有区别的.有两种情况,有区别.第一种 就是有的时候会因为某些原因 view会多次测量,那

Android View如何绘制

上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感"的外表.那么View又是如何进行绘制了? 要了解View如何绘制,就需要了解canvas(画布)是什么?paint(画笔)能够做什么. Ⅰ.canvas就是表示一块画布,你可以在上面画你所朝思暮想的东西.当我们重写onDraw方法的时候,就能够拿到一个Canvas对象,这个就是你的舞台,画你所思所

13问13答全面学习Android View绘制

本文通过13问13答学习Android View绘制,供大家参考,具体内容如下 1.View的绘制流程分几步,从哪开始?哪个过程结束以后能看到view? 答:从ViewRoot的performTraversals开始,经过measure,layout,draw 三个流程.draw流程结束以后就可以在屏幕上看到view了. 2.view的测量宽高和实际宽高有区别吗? 答:基本上百分之99的情况下都是可以认为没有区别的.有两种情况,有区别.第一种 就是有的时候会因为某些原因 view会多次测量,那第

浅谈Android View绘制三大流程探索及常见问题

View绘制的三大流程,指的是measure(测量).layout(布局).draw(绘制) measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/高和四个顶点在手机界面上的位置,等通过measure和layout过程确定了View的宽高和要显示的位置后,就会执行draw绘制View的内容到手机屏幕上. 在详细介绍这三大流程之前,需要简单了解一下ViewRootImpl,View绘制的三大步骤