Android新人求教问:如何自定义ViewGroup,望大神潜入、不吝赐教。
3张扑克牌叠在一起显示效果如下:
这个布局效果可以用该RelativeLayout或FrameLayout,然后为每一个扑克牌设置margin就能实现,不过我觉得这种方式有点low,谁可以告知高级一点的实现方式啊,求告知~
回复内容:
3张扑克牌叠在一起显示效果如下:
这个布局效果可以用该RelativeLayout或FrameLayout,然后为每一个扑克牌设置margin就能实现,不过我觉得这种方式有点low,谁可以告知高级一点的实现方式啊,求告知~
除了你说的那种,我们还可以用ViewGroup实现。不过在定制ViewGroup之前,我们需要先理解一些定义。
Android绘制视图的方式。“绘制布局由两个遍历过程组成:测量过程和布局过程。测量过程由measure(int, int)方法完成,该方法从上到下遍历视图树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当measure方法遍历结束,每个视图都保存了各自的尺寸信息。第二个过程由 layout(int,int,int,int)方法完成,该方法也是由上而下遍历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所有子视图的位置信息。”
简而言之,第一步是测量ViewGroup的宽度和高度,在onMeasure()方法中完成,ViewGroup遍历所有子视图计算出它的大小。第二步是根据第一步获取的尺寸去布局所有子视图,在onLayout()中完成。
创建CascadeLayout
终于到了定制ViewGroup的阶段了。假设我们已经定制了一个CascadeLayout的容器,我们会这样使用它。
1.
3. xmlns:android="http://schemas.android.com/apk/res/android"
4. android:layout_width="fill_parent"
5. android:layout_height="fill_parent" >
6.
7.
9. android:layout_height="fill_parent"
10.
11. cascade:horizontal_spacing="30dp"
12. cascade:vertical_spacing="20dp" >
13.
14.
16. android:layout_height="150dp"
17. android:background="#FF0000" />
18.
19.
21. android:layout_height="150dp"
22. android:background="#00FF00" />
23.
24.
26. android:layout_height="150dp"
27. android:background="#0000FF" />
28.
29.
30.
首先,定义属性。在values文件夹下面创建attrs.xml,代码如下:
1.
2.
3.
4.
5.
6.
同时,为了严谨一些,定义一些默认的垂直距离和水平距离,以防在布局中没有提供这些属性。
在dimens.xml中添加如下代码:
1.
2.
3.
4.
准备工作已经做好了,接下来看一下CascadeLayout的源码,略微有点长,后面帮助大家分析一下。
1.public class CascadeLayout extends ViewGroup {
2.
3. private int mHorizontalSpacing;
4. private int mVerticalSpacing;
5.
6. public CascadeLayout(Context context, AttributeSet attrs) {
7. super(context, attrs);
8.
9. TypedArray a = context.obtainStyledAttributes(attrs,
10. R.styleable.CascadeLayout);
11.
12. try {
13. mHorizontalSpacing = a.getDimensionPixelSize(
14. R.styleable.CascadeLayout_horizontal_spacing,
15. getResources().getDimensionPixelSize(
16. R.dimen.cascade_horizontal_spacing));
17.
18. mVerticalSpacing = a.getDimensionPixelSize(
19. R.styleable.CascadeLayout_vertical_spacing, getResources()
20. .getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
21. } finally {
22. a.recycle();
23. }
24.
25. }
26.
27. @Override
28. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
29. int width = getPaddingLeft();
30. int height = getPaddingTop();
31. int verticalSpacing;
32.
33. final int count = getChildCount();
34. for (int i = 0; i
35. verticalSpacing = mVerticalSpacing;
36.
37. View child = getChildAt(i);
38. measureChild(child, widthMeasureSpec, heightMeasureSpec);
39.
40. LayoutParams lp = (LayoutParams) child.getLayoutParams();
41. width = getPaddingLeft() + mHorizontalSpacing * i;
42.
43. lp.x = width;
44. lp.y = height;
45.
46. if (lp.verticalSpacing >= 0) {
47. verticalSpacing = lp.verticalSpacing;
48. }
49.
50. width += child.getMeasuredWidth();
51. height += verticalSpacing;
52. }
53.
54. width += getPaddingRight();
55. height += getChildAt(getChildCount() - 1).getMeasuredHeight()
56. + getPaddingBottom();
57.
58. setMeasuredDimension(resolveSize(width, widthMeasureSpec),
59. resolveSize(height, heightMeasureSpec));
60. }
61.
62. @Override
63. protected void onLayout(boolean changed, int l, int t, int r, int b) {
64.
65. final int count = getChildCount();
66. for (int i = 0; i
67. View child = getChildAt(i);
68. LayoutParams lp = (LayoutParams) child.getLayoutParams();
69.
70. child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
71. + child.getMeasuredHeight());
72. }
73. }
74.
75. @Override
76. protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
77. return p instanceof LayoutParams;
78. }
79.
80. @Override
81. protected LayoutParams generateDefaultLayoutParams() {
82. return new LayoutParams(LayoutParams.WRAP_CONTENT,
83. LayoutParams.WRAP_CONTENT);
84. }
85.
86. @Override
87. public LayoutParams generateLayoutParams(AttributeSet attrs) {
88. return new LayoutParams(getContext(), attrs);
89. }
90.
91. @Override
92. protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
93. return new LayoutParams(p.width, p.height);
94. }
95.
96. public static class LayoutParams extends ViewGroup.LayoutParams {
97. int x;
98. int y;
99. public int verticalSpacing;
100.
101. public LayoutParams(Context context, AttributeSet attrs) {
102. super(context, attrs);
103. }
104.
105. public LayoutParams(int w, int h) {
106. super(w, h);
107. }
108.
109. }
110.}
首先,分析构造函数。
1.public CascadeLayout(Context context, AttributeSet attrs) {
2. super(context, attrs);
3.
4. TypedArray a = context.obtainStyledAttributes(attrs,
5. R.styleable.CascadeLayout);
6.
7. try {
8. mHorizontalSpacing = a.getDimensionPixelSize(
9. R.styleable.CascadeLayout_horizontal_spacing,
10. getResources().getDimensionPixelSize(
11. R.dimen.cascade_horizontal_spacing));
12.
13. mVerticalSpacing = a.getDimensionPixelSize(
14. R.styleable.CascadeLayout_vertical_spacing, getResources()
15. .getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
16. } finally {
17. a.recycle();
18. }
19.
20. }
如果在布局中使用CasecadeLayout,系统就会调用这个构造函数,这个大家都应该知道的吧。这里不解释why,有兴趣的可以去看源码,重点看系统是如何解析xml布局的。
构造函数很简单,就是通过布局文件中的属性,获取水平距离和垂直距离。
然后,分析自定义LayoutParams。
这个类的用途就是保存每个子视图的x,y轴位置。这里把它定义为静态内部类。ps:提到静态内部类,我又想起来关于多线程内存泄露的问题了,如果有时间再给大家解释一下多线程造成内存泄露的问题。
1.public static class LayoutParams extends ViewGroup.LayoutParams {
2. int x;
3. int y;
4. public int verticalSpacing;
5.
6. public LayoutParams(Context context, AttributeSet attrs) {
7. super(context, attrs);
8. }
9.
10. public LayoutParams(int w, int h) {
11. super(w, h);
12. }
13.
14. }
除此之外,还需要重写一些方法,checkLayoutParams()、generateDefaultLayoutParams()等,这个方法在不同ViewGroup之间往往是相同的。
接下来,分析onMeasure()方法。
1.@Override
2.protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3. int width = getPaddingLeft();
4. int height = getPaddingTop();
5. int verticalSpacing;
6.
7. final int count = getChildCount();
8. for (int i = 0; i
9. verticalSpacing = mVerticalSpacing;
10.
11. View child = getChildAt(i);
12. measureChild(child, widthMeasureSpec, heightMeasureSpec); // 令每个子视图测量自身
13.
14. LayoutParams lp = (LayoutParams) child.getLayoutParams();
15. width = getPaddingLeft() + mHorizontalSpacing * i;
16. // 保存每个子视图的x,y轴坐标
17. lp.x = width;
18. lp.y = height;
19.
20. if (lp.verticalSpacing >= 0) {
21. verticalSpacing = lp.verticalSpacing;
22. }
23.
24. width += child.getMeasuredWidth();
25. height += verticalSpacing;
26. }
27.
28. width += getPaddingRight();
29. height += getChildAt(getChildCount() - 1).getMeasuredHeight()
30. + getPaddingBottom();
31. // 使用计算所得的宽和高设置整个布局的测量尺寸
32. setMeasuredDimension(resolveSize(width, widthMeasureSpec),
33. resolveSize(height, heightMeasureSpec));
34.}
最后,分析onLayout()方法。
1.@Override
2.protected void onLayout(boolean changed, int l, int t, int r, int b) {
3.
4. final int count = getChildCount();
5. for (int i = 0; i
6. View child = getChildAt(i);
7. LayoutParams lp = (LayoutParams) child.getLayoutParams();
8.
9. child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
10. + child.getMeasuredHeight());
11. }
12.}
逻辑很简单,用onMeasure()方法计算出的值为参数循环调用子View的layout()方法。
为子视图添加自定义属性
作为示例,下面将添加子视图重写垂直间距的方法。
第一步是向attrs.xml中添加一个新的属性。
1.
2.
3.
这里的属性名是layout_vertical_spacing,因为该属性名前缀是layout_,同时,又不是View固有的属性,所以该属性会被添加到LayoutParams的属性表中。在CascadeLayout类的构造函数中读取这个新属性。
1.public static class LayoutParams extends ViewGroup.LayoutParams {
2. int x;
3. int y;
4. public int verticalSpacing;
5.
6. public LayoutParams(Context context, AttributeSet attrs) {
7. super(context, attrs);
8.
9. TypedArray a = context.obtainStyledAttributes(attrs,
10. R.styleable.CascadeLayout_LayoutParams);
11. try {
12. verticalSpacing = a
13. .getDimensionPixelSize(
14. R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
15. -1);
16. } finally {
17. a.recycle();
18. }
19. }
20.
21. public LayoutParams(int w, int h) {
22. super(w, h);
23. }
24.
25. }
那怎么使用这个属性呢?so easy!
1.
3. android:layout_height="fill_parent"
4. cascade:horizontal_spacing="30dp"
5. cascade:vertical_spacing="20dp" >
6.
7.
8.
10. android:layout_height="150dp"
11. cascade:layout_vertical_spacing="90dp"
12. android:background="#FF0000" />
13.
14.
16. android:layout_height="150dp"
17. android:background="#00FF00" />
18.
19.
21. android:layout_height="150dp"
22. android:background="#0000FF" />
23.
其实你只需要搜索“创建定制的ViewGroup”就能找到正确的答案了。
right answer