首页 > 代码库 > android自定义控件系列教程----视图的测量和布局

android自定义控件系列教程----视图的测量和布局

前面说点什么

当我们的一个视图界面绘制在android屏幕上面的时候其实都必须经过这几步measure、 layout、draw这几个阶段,我们可以在view类里面看到这几个函数,然后里面有几个函数是onmeasure、onlayout、ondraw这几个函数是我们重写控件需要注意的这几个函数,下面我们就来讲讲这几个函数的功能和作用。

onMeasure

正如这个函数的名子一样就是测量,所有的图示其实系统在绘制之前都不知道它到底有多大的,所以在很多时候我们在初始化界面oncreate的时候直接去调用一个View的getwitfh或者getheight都获取不到,因为在oncreate的时候系统还没有统一的发出排版请求,说到了这里我们就仔细来分析一下这个onmeasuer函数到底要注意些什么,他回调给我们的参数是MeasureSpec,保存的是父亲的属性。MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:

1. EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

2. AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

3. UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

关于测量的问题可以看看LinearLayout、RelativeLayout这些是怎么实现的,加深理解。

onLayout

通过前面的onMeasure我们计算出了这个控件所占的宽高,那么我就就需要在这个onlayout函数里面去告诉系统它应该摆放在屏幕的哪个位置,由于我们基本上所有的控件都回去继承ViewGroup我们仔细的来研究一下它的onLayout方法吧。
   @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
可以看它是一个抽象的方法,所以我们所有的实现类都需要来实现这个方法,自己来告诉系统我们需要怎么去排版我们的界面,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。由于LinearLayout和RelativeLayout的布局规则都比较复杂,就不单独拿出来进行分析了。

实例讲解

这里我们结合一个例子来讲解上面提到的东西,我们将简单的实现一个垂直的布局。先看看效果吧。技术分享
我们要实现的就是一个类似于LinearLayout的垂直布局的容器,好了现在我们直接看代码吧,我把代码都注释在里面。我们还是按规矩来继承一个ViewGroup
public class SimpleVertical extends ViewGroup {

	public SimpleVertical(Context context) {
		super(context);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int totalheight = 0;
		int childcount = getChildCount();
		for (int i = 0; i < childcount; i++) {
			View childView = getChildAt(i);
			if (childView.getVisibility() != GONE) {
				childView.layout(0, totalheight, childView.getMeasuredWidth(), totalheight+childView.getMeasuredHeight()); //设置自己放置的位置
				totalheight+=childView.getMeasuredHeight();//计算高度的开始
			}
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int childCount = getChildCount();
		for (int i = 0; i < childCount; i++) {
			View childView = getChildAt(i);
			if (childView.getVisibility() != GONE) {
				LayoutParams childLp = childView.getLayoutParams(); // 获取layout参数
				int childWidthMeasureSpec = getChildMeasureSpec(
						widthMeasureSpec, getPaddingLeft() + getPaddingRight(),
						childLp.width); // 调用VIewGroup的封装的方法
				int childHeightMeasureSpec = getChildMeasureSpec(
						widthMeasureSpec, getPaddingTop() + getPaddingBottom(),
						childLp.height);
				childView
						.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 最重要的就是告诉自己测量
			}
		}
		// measureChildren(widthMeasureSpec, heightMeasureSpec);
		// 也可以调用ViewGroup这个方法
		super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 记住调用这个方法不然会报错,其实它也是调用的setMeasuredDimension这个方法
	}
}
所有的注释我都写在上面了然后我们来写一个测试的Activity,我直接贴出oncreate里面的代码了。也很简单
<span style="font-size:14px;">SimpleVertical simpleVertical = new SimpleVertical(this);
		for (int i = 0; i < 12; i++) {
			TextView textView = getTextView("android自定义控件系列教程----视图的测量和布局");
			textView.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View v) {
					Toast.makeText(MainActivity.this, "</span><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size:12px;">android自定义控件系列教程----视图的测量和布局</span></span><span style="font-size:14px;">", 0).show();
				}
			});
			simpleVertical.addView(textView);
		}

		setContentView(simpleVertical);</span>
可以看到我们不但把我们自己写得控件写上了,还给它添加了一个点击事件,点击上去也是没有问题的,这样我们就简单的实现了Linearlayotu的垂直布局了,想要跟深入的理解这里的写法,我的建议是去看Linearlayotu,RelaytiveLayout这一类容器控件的写法。

android自定义控件系列教程----视图的测量和布局