android view绘制流程

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.top + 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="schemas.android.com/apk/res/and…"
xmlns:tools="schemas.android.com/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.top + 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="schemas.android.com/apk/res/and…"
xmlns:tools="schemas.android.com/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);

}


转载于:https://juejin.im/post/5c26e03b6fb9a049af6d5686

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值