前言:
我们将说点什么:
1.view/viewgroup的绘制机制
你要往一个画布上绘制点东西,那么你需要确认几点:要绘制的东西是什么,它有多大,它应该被绘制到画布的哪个位置
也许在android之前,你动手设计过自己的绘制系统,也许,现在,你可以检验一下,你的和android的,是否相似,哪一个更好
2.如何从头开始,构建自己的layout(不要继承于现有的layout.class),利用我们之前的第1点知识
************************************************************正文
1.view/viewgroup的绘制机制
如果要你来设计一个绘制系统,你会怎么来做呢
在这个系统中,你要考虑的第一步往往是这些:
- 我应该提供一个基类,所有的view都继承于该基类
- 也许我还应提供一些现有的widget,以便使用我的系统的第三方的开发者使用
- 一副画往往不止由一个部件组成,各个部件需要按照特定的布局,依次陈列于画布上,那么,我需要向上抽象出布局基类
- 也许我还可以提供一些现有的布局,以便使用我的系统的第三方的开发者使用
- ....
我们已经思考了上述的需求,我们得到了基类view,一些已经实现的widget(如button, textview),基类viewgroup,一些已经实现的layout(如linearlayout, relativelayout)
嗯,很好的开始,接下来,思考下面的问题:
1.widget怎么绘制呢?嗯,这是一个问题,我能为第三方开发者做些什么吗?
因为每个widget的绘制都有自身的特点,所以,我不能提供“除了制定流程”之外的任何帮助
android提供了钩子:ondraw
2.我们脑海中已经抽象了布局基类,那么,我们需要提供布局的流程
你会怎么来放置你的view东西呢?
a.提供你的方式:你是1个东西放1行;还是所有的东西从左往右摆一行,空间不够了摆下一行
b.让我冥想一些抽象的东西:每个东西的大小是怎么样的
c.我自身可能作为一个新的东西,以让别人来布局我:提供自己的大小
上面讲述的,就是android考虑的:
a的实现通过onlayout来完成,在该方法中,你将被layout,如果是非viewgroup,被layout就意味着进行绘制,如果是viewgroup,被layout就意味着:请你来layout你的孩子
b和c的实现通过onmeasure来完成,在该方法中,你将被丈量,如果是非viewgroup,请丈量出自身的大小(举个例子,如果你是textview,请丈量出自身的文字做占用了多少像素),如果是viewgroup,请依次丈量你的孩子(这样,孩子就告诉你,它需要多少空间),并在脑子中浮现出你的布局style,假设一下,你按照你的style来layout完你的所有孩子后,你需要多大的空间
让我们再针对于 丈量 来讨论的更多一点
view可以使用之下方式,来表明自己占用多少空间:wrap_content, fill_parent/match_parent, 一个显式的像素值
当要决定view占用了多少空间时,onmeasure就会被调用,而之前你声明的方式,一样会作为参数传递给你
并且,还给了你一些额外的东西:父亲告诉你:当前还剩下多少可分配的空间
这两部分信息被封装成为了measurespec,并做了稍许改变,传递给了你
举个例子
如果你声明的是wrap_content,那么父亲传递给你的measurespec为[at_most,剩余的空间]
如果是match_parent,那么,相应的就是[exactly,剩余的空间]
...
你拿到这个spec的时候,可以完全忽视它,当然,最佳实践是,你参考它们
并在最后丈量出自己:setMeasuredDimension
然后父亲就知道了你的丈量结果:childview.getmeasuredwidth/height
然后父亲就根据你的结果,再根据自身的布局style,丈量出自身的大小
让我们再针对于 布局东西 来讨论的更多一点
这里的操作类似于这样:
我已经知道了这个东西的measured dimension,然后我也明白自己的style,
我当然知道,按照我的style,这个东西要被layout到什么位置(相对于我自身的0,0点的坐标)
然后,我调用childview.layout
that's it
2.构建自己的layout
如果你完全清楚我们之前讲的,那么你将很容易构建自己的layout
我提供了,我的style:
将所有孩子放置到一行上,如果该行已经没有空间放置孩子了,那么该孩子放入到下一行
do you get it
import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.ql.testMyViewGroup.R; public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub TypedArray taMyAttr = context.obtainStyledAttributes(attrs, R.styleable.mystyleattr); gap = taMyAttr.getInt(R.styleable.mystyleattr_gap, 10); taMyAttr.recycle(); } public MyViewGroup(Context context) { super(context); // TODO Auto-generated constructor stub } /** * 孩子之间的gap,不考虑孩子的margin(因为myviewgroup不是linearlayout,所以lp也不包含margin) */ private int gap; private int maxWidth; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); maxWidth = MeasureSpec.getSize(widthMeasureSpec); /** * 假设所有的padding都是相同的 */ int padding = getPaddingBottom(); int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int width = -gap; int height = padding + padding; int count = getChildCount(); for (int i = 0; i < count; i++) { View view = getChildAt(i); view.measure(spec, spec); width += gap + view.getMeasuredWidth(); if (i == 0) { height += view.getMeasuredHeight(); } if (width > maxWidth - padding - padding) { width = -gap; height += view.getMeasuredHeight() + gap; } System.out.println("---dimension:" + view.getWidth() + "," + view.getHeight()); System.out.println("---measured dimension:" + view.getMeasuredWidth() + "," + view.getMeasuredHeight()); } setMeasuredDimension(maxWidth, height); System.out.println("---self dimension:" + getWidth() + "," + getHeight()); System.out.println("---self measured dimension:" + getMeasuredWidth() + "," + getMeasuredHeight()); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { System.out.println("on layout"); // TODO Auto-generated method stub int count = getChildCount(); int padding = getPaddingBottom(); int cl = padding; int ct = padding; int height = 0; for (int i = 0; i < count; i++) { View view = getChildAt(i); if (cl + gap + view.getMeasuredWidth() + padding > maxWidth) { cl = padding; ct += height + gap; } height = view.getMeasuredHeight(); view.layout(cl, ct, cl + view.getMeasuredWidth(), ct + view.getMeasuredHeight()); cl += view.getMeasuredWidth() + gap; System.out.println("dimension:" + view.getWidth() + "," + view.getHeight()); System.out.println("measured dimension:" + view.getMeasuredWidth() + "," + view.getMeasuredHeight()); } System.out.println("self dimension:" + getWidth() + "," + getHeight()); System.out.println("self measured dimension:" + getMeasuredWidth() + "," + getMeasuredHeight()); } }