android view是大家实现各种漂亮ui的基础,因此对于它的重要性,就可想而知了;网上关于android view分析的文章也是非常的多,之所以还写这篇文章主要还是,通过看大家的分析和自己的理解做一个整理和记录,这样会有个更加深刻的印象。

android view 有几万行的代码,

本文主要针对view绘制流程的主要三个方法进行分析:测量(Measure)、布局(Layout)、绘制(draw)

 

想要搞懂一个知识,最好的办法是通过实例来分析,所以这里我写了个简单的例子来看下Activity 中setContentView()时view三个方法的流程:

(1)自定义的一个view,重写onMeasure、onLayout、onDraw方法:

public class CustomView extends View {
     private static final String TAG = "CustomView";
    public CustomView(Context context) {
         super(context);
         Log.e(TAG,"构造方法");
     }

     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         Log.e(TAG,"onMeasure");

     }

     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         Log.e(TAG,"onLayout");
     }

     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         Log.e(TAG,"onDraw");
     }
 }

 

(2)activity 中使用:

@Override
protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
    final CustomView view = new CustomView(this);
     view.setBackgroundResource(R.drawable.ic_launcher_background);
     setContentView(view);
 }

跑下程序看下log 跑的结果:

activity android 绘制 androidview的绘制流程_Android view

可以看到三个方法的执行顺序是:onMeasure->onLayout->onDraw,但是这里执行了三次onMeasure两次onLayout,

这是为什么呢?

DecorView,而DecorView是FrameLayout的子类,我们看FrameLayout的源码,会调用两次onMeasure,而在onLayout后又执行onMeasure->onLayout这个过程,视图大小发生变化然后调用requestLayout()方法,而requestLayout()会导致调用measure()过程 和 layout()过程 。

 

知道了这三个方法的执行流程,我们来分别解释下这三个方法的作用:

 

1.onMeasure方法:

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

我们看到onMeasure源码中调用了setMeasuredDimension方法,setMeasuredDimension源码:

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.bottom;

         measuredWidth  += optical ? opticalWidth  : -opticalWidth;
         measuredHeight += optical ? opticalHeight : -opticalHeight;
     }
     setMeasuredDimensionRaw(measuredWidth, measuredHeight);
 }private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
     mMeasuredWidth = measuredWidth;
     mMeasuredHeight = measuredHeight;

     mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
 }


 

可以看到setMeasuredDimension()就是真正的测量视图的大小,测量一个view实际上是给字段mMeasuredWidth,mMeasuredHeight设置值,最后执行mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET,将字段mPrivateFlags的EASURED_DIMENSION_SET位设置为1。

这时候我们实例中调用下setMeasuredDimension:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     setMeasuredDimension(1000,1000);
     Log.e(TAG,"onMeasure");}

 

activity android 绘制 androidview的绘制流程_activity android 绘制_02

如果没重写setMeasuredDimension方法,默认是满屏的,调用后就设置了视图的大小.

接下来我们通过另一个方式来了解下onMeasure方法设置视图大小与父视图的关系:

<android.support.constraint.ConstraintLayout xmlns:android="http:///apk/res/android"
     xmlns:tools="http:///tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">

    <com.example.linwenbing.demo.CustomView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="#000000"
         /></android.support.constraint.ConstraintLayout>

我这边自定义的view父视图设置的宽高都是match_parent 而view本事宽高是wrap_content,运行发现,子视图需然设置的wrap_content但是确布满整个屏幕,但是当自定义view设置具体的宽高时,就是根据具体的宽高显示所以得出结论:

当子视图没有设置具体宽高时,子视图采用的是父视图的宽高。这是什么原因呢?看源码:

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


 

我们发现AT_MOST (相当于wrap_content )和EXACTLY (相当于match_parent )两种情况返回的测量宽高都是specSize,而这个specSize正是我们上面说的父控件剩余的宽高,所以默认onMeasure方法中wrap_content 和match_parent 的效果是一样的,都是填充剩余的空间。

 

要真正理解onMeasure我们还需要知道onMeasure两个参数int widthMeasureSpec, int heightMeasureSpec的意思:

这两个参数并不是真正的宽高,这两个参数中包含了两个意思:MeasureSpec类中的specMode和specSize

MeasureSpec类中specMode的三种模式:

public static class MeasureSpec {
     private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

     /** @hide */
     @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
     @Retention(RetentionPolicy.SOURCE)
     public @interface MeasureSpecMode {}

     /**
     * Measure specification mode: The parent has not imposed any constraint
     * on the child. It can be whatever size it wants.
     * 父控件不强加任何约束给子控件,它可以是它想要任何大小
     */
     public static final int UNSPECIFIED = 0 << MODE_SHIFT;

     /**
     * Measure specification mode: The parent has determined an exact size
     * for the child. The child is going to be given those bounds regardless
     * of how big it wants to be.
     * 父控件已为子控件确定了一个确切的大小,孩子将被给予这些界限,不管子控件自己希望的是多大
     */
     public static final int EXACTLY     = 1 << MODE_SHIFT;

     /**
     * Measure specification mode: The child can be as large as it wants up
     * to the specified size.
     * 父控件会给子控件尽可能大的尺寸
     */
    
     public static final int AT_MOST     = 2 << MODE_SHIFT;

 

specSize:父控件传过来的大小。

onMeasure方法总结:

(1)onMeasure (int widthMeasureSpec, int heightMeasureSpec)是view自己的方法

(2)onMeasure 方法简单的理解就是是用于测量视图的大小,主要是用来测量自己和内容的来确定宽度和高度

(3)onMeasure有两个参数( int widthMeasureSpec, int heightMeasureSpec),该参数表示控件可获得的空间以及关于这个空间描述的元数据.

(4)widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。

2.onLayout方法:

(1)首页我们看onLayout的源码:

/**
 * Called from layout when this view should
 * assign a size and position to each of its children.
 *
 * Derived classes with children should override
 * this method and call layout on each of
 * their children.
 * @param changed This is a new size or position for this view
 * @param left Left position, relative to parent
 * @param top Top position, relative to parent
 * @param right Right position, relative to parent
 * @param bottom Bottom position, relative to parent
 */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 }

会发现什么都没有实现,这是因为:

这个其实是android留给我们自己去实现的一个方法,也就是大家都知道的,去布局子View的位置,只有含有子View的容器,才需要重写这个方法,也就是ViewGroup。

而view是通过layout方法来确认自己在父容器中的位置。

viewgroup自定义的onLayout:

 

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
     int hadUsedHorizontal = 0;//水平已经使用的距离
     int hadUsedVertical = 0;//垂直已经使用的距离
     int width = getMeasuredWidth();
    for (int i = 0; i < getChildCount(); i++) {
         View view = getChildAt(i);
        if (view.getMeasuredWidth() + hadUsedHorizontal > width) {
             hadUsedVertical = hadUsedVertical + view.getMeasuredHeight() + verticalSpace;
             hadUsedHorizontal = 0;
         }
         view.layout(hadUsedHorizontal, hadUsedVertical, hadUsedHorizontal + view.getMeasuredWidth(), hadUsedVertical + view.getMeasuredHeight());
         hadUsedHorizontal = hadUsedHorizontal + horizontalSpace + view.getMeasuredWidth();
     }

 }

onLayout总结:

onLayout主要是viewgroup用来确定子view在父容器中的位置,所以在自定义viewgroup时需要重写onLayout.

 

3.onDraw方法:

 onDraw方法比较简单,主要就是把view绘制到屏幕上,看源码:

/**
 * Implement this to do your drawing.
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) {}

也是一个没有任何实现的方法,主要是提供给view自己绘制,onDraw怎么绘制,这里不做详细说,就写个简单绘制一个位于中心的圆:

@Override
protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     Log.e(TAG,"onDraw");
     canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,150,mPaint);

 }

android view是大家实现各种漂亮ui的基础,因此对于它的重要性,就可想而知了;网上关于android view分析的文章也是非常的多,之所以还写这篇文章主要还是,通过看大家的分析和自己的理解做一个整理和记录,这样会有个更加深刻的印象。

android view 有几万行的代码,

本文主要针对view绘制流程的主要三个方法进行分析:测量(Measure)、布局(Layout)、绘制(draw)

 

想要搞懂一个知识,最好的办法是通过实例来分析,所以这里我写了个简单的例子来看下Activity 中setContentView()时view三个方法的流程:

(1)自定义的一个view,重写onMeasure、onLayout、onDraw方法:

public class CustomView extends View {
     private static final String TAG = "CustomView";
    public CustomView(Context context) {
         super(context);
         Log.e(TAG,"构造方法");
     }

     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         Log.e(TAG,"onMeasure");

     }

     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         Log.e(TAG,"onLayout");
     }

     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         Log.e(TAG,"onDraw");
     }
 }

(2)activity 中使用:

@Override
protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
    final CustomView view = new CustomView(this);
     view.setBackgroundResource(R.drawable.ic_launcher_background);
     setContentView(view);
 }

跑下程序看下log 跑的结果:

 

可以看到三个方法的执行顺序是:onMeasure->onLayout->onDraw,但是这里执行了三次onMeasure两次onLayout,

这是为什么呢?

DecorView,而DecorView是FrameLayout的子类,我们看FrameLayout的源码,会调用两次onMeasure,而在onLayout后又执行onMeasure->onLayout这个过程,视图大小发生变化然后调用requestLayout()方法,而requestLayout()会导致调用measure()过程 和 layout()过程 。

 

知道了这三个方法的执行流程,我们来分别解释下这三个方法的作用:

 

1.onMeasure方法:

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

我们看到onMeasure源码中调用了setMeasuredDimension方法,setMeasuredDimension源码:

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.bottom;

         measuredWidth  += optical ? opticalWidth  : -opticalWidth;
         measuredHeight += optical ? opticalHeight : -opticalHeight;
     }
     setMeasuredDimensionRaw(measuredWidth, measuredHeight);
 }private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
     mMeasuredWidth = measuredWidth;
     mMeasuredHeight = measuredHeight;

     mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
 }

可以看到setMeasuredDimension()就是真正的测量视图的大小,测量一个view实际上是给字段mMeasuredWidth,mMeasuredHeight设置值,最后执行mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET,将字段mPrivateFlags的EASURED_DIMENSION_SET位设置为1。

这时候我们实例中调用下setMeasuredDimension:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     setMeasuredDimension(1000,1000);
     Log.e(TAG,"onMeasure");}

如果没重写setMeasuredDimension方法,默认是满屏的,调用后就设置了视图的大小.

接下来我们通过另一个方式来了解下onMeasure方法设置视图大小与父视图的关系:

<android.support.constraint.ConstraintLayout xmlns:android="http:///apk/res/android"
     xmlns:tools="http:///tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".MainActivity">

    <com.example.linwenbing.demo.CustomView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="#000000"
         />
  </android.support.constraint.ConstraintLayout>

我这边自定义的view父视图设置的宽高都是match_parent 而view本事宽高是wrap_content,运行发现,子视图需然设置的wrap_content但是确布满整个屏幕,但是当自定义view设置具体的宽高时,就是根据具体的宽高显示所以得出结论:

当子视图没有设置具体宽高时,子视图采用的是父视图的宽高。这是什么原因呢?看源码:

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

我们发现AT_MOST (相当于wrap_content )和EXACTLY (相当于match_parent )两种情况返回的测量宽高都是specSize,而这个specSize正是我们上面说的父控件剩余的宽高,所以默认onMeasure方法中wrap_content 和match_parent 的效果是一样的,都是填充剩余的空间。

 

要真正理解onMeasure我们还需要知道onMeasure两个参数int widthMeasureSpec, int heightMeasureSpec的意思:

这两个参数并不是真正的宽高,这两个参数中包含了两个意思:MeasureSpec类中的specMode和specSize

MeasureSpec类中specMode的三种模式:

public static class MeasureSpec {
     private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

     /** @hide */
     @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
     @Retention(RetentionPolicy.SOURCE)
     public @interface MeasureSpecMode {}

     /**
     * Measure specification mode: The parent has not imposed any constraint
     * on the child. It can be whatever size it wants.
     * 父控件不强加任何约束给子控件,它可以是它想要任何大小
     */
     public static final int UNSPECIFIED = 0 << MODE_SHIFT;

     /**
     * Measure specification mode: The parent has determined an exact size
     * for the child. The child is going to be given those bounds regardless
     * of how big it wants to be.
     * 父控件已为子控件确定了一个确切的大小,孩子将被给予这些界限,不管子控件自己希望的是多大
     */
     public static final int EXACTLY     = 1 << MODE_SHIFT;

     /**
     * Measure specification mode: The child can be as large as it wants up
     * to the specified size.
     * 父控件会给子控件尽可能大的尺寸
     */
    
     public static final int AT_MOST     = 2 << MODE_SHIFT;

specSize:父控件传过来的大小。

onMeasure方法总结:

(1)onMeasure (int widthMeasureSpec, int heightMeasureSpec)是view自己的方法

(2)onMeasure 方法简单的理解就是是用于测量视图的大小,主要是用来测量自己和内容的来确定宽度和高度

(3)onMeasure有两个参数( int widthMeasureSpec, int heightMeasureSpec),该参数表示控件可获得的空间以及关于这个空间描述的元数据.

(4)widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。

2.onLayout方法:

(1)首页我们看onLayout的源码:

/**
 * Called from layout when this view should
 * assign a size and position to each of its children.
 *
 * Derived classes with children should override
 * this method and call layout on each of
 * their children.
 * @param changed This is a new size or position for this view
 * @param left Left position, relative to parent
 * @param top Top position, relative to parent
 * @param right Right position, relative to parent
 * @param bottom Bottom position, relative to parent
 */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 }

会发现什么都没有实现,这是因为:

这个其实是android留给我们自己去实现的一个方法,也就是大家都知道的,去布局子View的位置,只有含有子View的容器,才需要重写这个方法,也就是ViewGroup。

而view是通过layout方法来确认自己在父容器中的位置。

viewgroup自定义的onLayout:

 

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
     int hadUsedHorizontal = 0;//水平已经使用的距离
     int hadUsedVertical = 0;//垂直已经使用的距离
     int width = getMeasuredWidth();
    for (int i = 0; i < getChildCount(); i++) {
         View view = getChildAt(i);
        if (view.getMeasuredWidth() + hadUsedHorizontal > width) {
             hadUsedVertical = hadUsedVertical + view.getMeasuredHeight() + verticalSpace;
             hadUsedHorizontal = 0;
         }
         view.layout(hadUsedHorizontal, hadUsedVertical, hadUsedHorizontal + view.getMeasuredWidth(), hadUsedVertical + view.getMeasuredHeight());
         hadUsedHorizontal = hadUsedHorizontal + horizontalSpace + view.getMeasuredWidth();
     }

 }

onLayout总结:

onLayout主要是viewgroup用来确定子view在父容器中的位置,所以在自定义viewgroup时需要重写onLayout.

 

3.onDraw方法:

 onDraw方法比较简单,主要就是把view绘制到屏幕上,看源码:

/**
 * Implement this to do your drawing.
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) {}

也是一个没有任何实现的方法,主要是提供给view自己绘制,onDraw怎么绘制,这里不做详细说,就写个简单绘制一个位于中心的圆:

@Override
protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     Log.e(TAG,"onDraw");
     canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,150,mPaint);

 }