Android自定义View解析之View的绘制流程(一)

在了解自定义View之前,首先需要知道View系统的绘制流程是从ViewRootperformTraversals()方法中开始的,然后在其内部调用Viewmeasure()方法对View进行测量,在measure()方法结束后,继续会在该方法内调用Viewlayout()方法来对视图进行布局,在layout()结束后,便会继续在该方法内调用Viewdraw()方法来绘制视图,至此,View的绘制流程结束。

ViewRoot中的代码如下:

private void performTraversals() {
	final View host = mView;
	...
	host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	...
	host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
	...
	draw(fullRedrawNeeded);
}

View的绘制主要涉及三个函数:

onMeasure():测量View和其内容已决定它的测量宽高。

onLayout():当这个视图应该给每个孩子分配一个大小和位置的时候调用。

onDraw():绘制视图

接下来我们来对View的源码进行分析,以加深对View的绘制流程的理解。

1、onMeasure()

前面我们已经知道,View的绘制流程是从measure开始的,接下来就来分析mesure的源码:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
........
   if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
...........
}

这里只列出核心代码,measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。而在该方法内核心就是调用了onMeasure()方法接下来让我们看看onMeasure()的源码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	setMeasuredDimension(
			getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
			getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我们先看看getDefaultSize()是干什么的,如下: 该方法比较简单,在方法里调用getDefaultSize() setMeasuredDimension(),

 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;
    }
之后会在 onMeasure() 方法中调用 setMeasuredDimension() 方法来储存测量的宽度和测量高度。这样 measure 过程就结束了。在方法里面根据传入的参数先是解析出 View 宽高的 mode, 然后在根据 mode 得到宽高的大小值 size

通过上面对源码的分析,我们已经知道Viewmeasure的过程了,下面我们对measure过程中的相关知识点进行说明。

1onMeasure()方法的两个参数信息:widthMeasureSpecheightMeasureSpec

这两个值分别用于确定视图的宽度和高度的模式和大小,可以通过MeasureSpec解析出来。MeasureSpec包括specModespecSize即模式和大小。

1.模式

模式包括"EXACTLY","AT_MOST"及"UNSPECIFIED"三种。可以通过Messure.getMode()获取。

EXACTLY(具体的值为 1 << 30 即为:2^30): 表示父视图希望子视图的大小应该是由specSize的值来决定的。例如在配置文件中,将View的大小设置为"固定的大小"或”ATCH_PARENT"。

AT_MOST(具体的值为 2 << 30 即为:2^31): 子视图最多只能是specSize中指定的大小。例如在配置文件中,将View设置为"wrap_content"。

UNSPECIFIED(具体的值为0) : 父视图对于子视图没有任何限制,开发人员可以将视图按照自己的意愿设置成任意的大小。

2. 大小

可以通过MeasureSpec.getSize()来获取。不过需要注意的是,通过该方法获取的大小是配置文件中定义的大小。如果我们自己对该View有特殊的大小要求,则需要根据情况进行处理。

分析完View的绘制流程,接下来让我们看看ViewGroup的绘制流程。

因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。因此ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
	final int size = mChildrenCount;
	final View[] children = mChildren;
	for (int i = 0; i < size; ++i) {
		final View child = children[i];
		if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
			measureChild(child, widthMeasureSpec, heightMeasureSpec);
		}
	}

在该方法中会去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小,如下:

protected void measureChild(View child, int parentWidthMeasureSpec,
		int parentHeightMeasureSpec) {
	final LayoutParams lp = child.getLayoutParams();
//根据ViewGroup的widthMeasureSpec和heightMeasureSpec以及View自身的布局 
     从而确定每个子View的widthMeasureSpec和heightMeasureSpec. 
	final int childWidthMeasureSpec = getChildMeasureSpec(
			parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
	final int childHeightMeasureSpec = getChildMeasureSpec(
			parentHeightMeasureSpec, mPaddingTop + mPaddingBottom,
			lp.height);

	child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

而在该方法中最终调用子视图的measure()方法,并把计算出的MeasureSpec传递进去,之后的流程就和前面所介绍的一样。

以上就是系统自动测量View的过程,当然我们如果不想使用系统默认的测量方式,可以按照自己的意愿进行定制只需重写onMeasure()方法中并在放方法中调用setMeasuredDimension()即可。特别提醒:在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,之前调用这两个方法得到的值都会是0。

综上所述,我们知道视图大小的控制是由父视图、布局文件、和视图本身共同完成的,父视图会提供给子视图参考的大小,开发人员可以在XML文件中指定视图的大小,最后视图本身会决定自身的大小。

2、onLayout()

measure过程结束后,视图的大小就已经测量好了,ViewRoot的performTraversals()方法会在measure()结束后调用View的layout()方法来确定视图的位置,如下:

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); 

layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言。从最后两个参数可以看到,把measure中测量出的宽度和高度传到了该layout()方法中.接下来看看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;
}

在看方法中首先判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重,接下来会调用onLayout()方法。

观察源码你会发现View中的onLayout()方法是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。

而ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法否则其中的控件是无法显示的.

在measure和layout都结束了,我们来区分下width和measureWidth的区别:

1)调用的时间不同

getMeasureWidth()在measure()过程结束后就可以获取到了

getWidth()方法要在layout()过程结束后才能获取到。

2)计算方式不同

getMeasureWidth()方法的值是通过setMeasuredDimension()方法来进行设置

getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

3、onDraw()

ViewRootperformTraversals()方法会在layout()结束后调用Viewdraw()方法来开始对视图进行绘制

源码中此方法共分为六步,一般2和5步时不需要的,draw的步骤如下:

1.draw the background,

3.Draw view's content

4.Draw children

6.Draw decorations (scrollbars for instance)

第三步骤核心:onDraw(canvas)方法,需要用到Canvas类,可查阅API了解其功能。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值