本文翻译自《50 android hacks》按照惯例。先从一个样例说起。非常easy,3张扑克牌叠在一起显示。这个布局效果该怎样实现呢?有的同学该说了,这非常easy啊,用RelativeLayout或FrameLayout,然后为每一个扑克牌设置margin就能实现了。ok,那就看一下通过这样的方式是怎样实现的。代码例如以下: android:layout_width="fill_parent" android:layout_height="fill_parent" > android:layout_width="100dp" android:layout_height="150dp" android:background="#FF0000" /> android:layout_width="100dp" android:layout_height="150dp" android:layout_marginLeft="30dp" android:layout_marginTop="20dp" android:background="#00FF00" /> android:layout_width="100dp" android:layout_height="150dp" android:layout_marginLeft="60dp" android:layout_marginTop="40dp" android:background="#0000FF" /> xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > android:layout_width="fill_parent" android:layout_height="fill_parent" cascade:horizontal_spacing="30dp" cascade:vertical_spacing="20dp" > android:layout_width="100dp" android:layout_height="150dp" android:background="#FF0000" /> android:layout_width="100dp" android:layout_height="150dp" android:background="#00FF00" /> android:layout_width="100dp" android:layout_height="150dp" android:background="#0000FF" />
同一时候,为了严谨一些,定义一些默认的垂直距离和水平距离,以防在布局中没有提供这些属性。在dimens.xml中加入例如以下代码:
准备工作已经做好了。接下来看一下CascadeLayout的源代码,稍微有点长,后面帮助大家分析一下。public class CascadeLayout extends ViewGroup {
private int mHorizontalSpacing;
private int mVerticalSpacing;
public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
try {
mHorizontalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing, getResources()
.getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
} finally {
a.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getPaddingLeft();
int height = getPaddingTop();
int verticalSpacing;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
verticalSpacing = mVerticalSpacing;
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = getPaddingLeft() + mHorizontalSpacing * i;
lp.x = width;
lp.y = height;
if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
}
width += child.getMeasuredWidth();
height += verticalSpacing;
}
width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight()
+ getPaddingBottom();
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
+ child.getMeasuredHeight());
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
}
首先,分析构造函数。public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout);
try {
mHorizontalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_horizontal_spacing,
getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing, getResources()
.getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
} finally {
a.recycle();
}
}假设在布局中使用CasecadeLayout,系统就会调用这个构造函数,这个大家都应该知道的吧。这里不解释why。有兴趣的能够去看源代码,重点看系统是怎样解析xml布局的。构造函数非常easy,就是通过布局文件里的属性,获取水平距离和垂直距离。然后。分析自己定义LayoutParams。这个类的用途就是保存每一个子视图的x,y轴位置。这里把它定义为静态内部类。ps:提到静态内部类。我又想起来关于多线程内存泄露的问题了,假设有时间再给大家解释一下多线程造成内存泄露的问题。public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutParams(int w, int h) {
super(w, h);
}
}除此之外。还须要重写一些方法。checkLayoutParams()、generateDefaultLayoutParams()等,这种方法在不同ViewGroup之间往往是同样的。接下来。分析onMeasure()方法。 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getPaddingLeft();
int height = getPaddingTop();
int verticalSpacing;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
verticalSpacing = mVerticalSpacing;
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec); // 令每一个子视图測量自身
LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = getPaddingLeft() + mHorizontalSpacing * i;
// 保存每一个子视图的x。y轴坐标
lp.x = width;
lp.y = height;
if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
}
width += child.getMeasuredWidth();
height += verticalSpacing;
}
width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight()
+ getPaddingBottom();
// 使用计算所得的宽和高设置整个布局的測量尺寸
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}最后,分析onLayout()方法。 @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
+ child.getMeasuredHeight());
}
}逻辑非常easy。用onMeasure()方法计算出的值为參数循环调用子View的layout()方法。为子视图加入自己定义属性作为演示样例。以下将加入子视图重写垂直间距的方法。第一步是向attrs.xml中加入一个新的属性。
这里的属性名是layout_vertical_spacing,由于该属性名前缀是layout_。同一时候,又不是View固有的属性。所以该属性会被加入到LayoutParams的属性表中。在CascadeLayout类的构造函数中读取这个新属性。public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout_LayoutParams);
try {
verticalSpacing = a
.getDimensionPixelSize(
R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
-1);
} finally {
a.recycle();
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
}
那怎么使用这个属性呢?so easy! android:layout_width="fill_parent" android:layout_height="fill_parent" cascade:horizontal_spacing="30dp" cascade:vertical_spacing="20dp" > android:layout_width="100dp" android:layout_height="150dp" cascade:layout_vertical_spacing="90dp" android:background="#FF0000" /> android:layout_width="100dp" android:layout_height="150dp" android:background="#00FF00" /> android:layout_width="100dp" android:layout_height="150dp" android:background="#0000FF" />
好文链接
发表评论