前面说点什么
当我们的一个视图界面绘制在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这一类容器控件的写法。