前言
我们将说点什么:
1.view/viewgroup的绘制机制
你要往一个画布上绘制点东西,那么你需要确认几点:要绘制的东西是什么,它有多大,它应该被绘制到画布的哪个位置
也许在android之前,你动手设计过自己的绘制系统,也许,现在,你可以检验一下,你的和android的,是否相似,哪一个更好
2.如何从头开始,构建自己的layout(不要继承于现有的layout.class),利用我们之前的第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
构建自己的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());
}
}