欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

Android新人求教问:如何自定义ViewGroup,望大神潜入、不吝赐教。

程序员文章站 2022-05-16 13:39:25
...

3张扑克牌叠在一起显示效果如下:Android新人求教问:如何自定义ViewGroup,望大神潜入、不吝赐教。
这个布局效果可以用该RelativeLayout或FrameLayout,然后为每一个扑克牌设置margin就能实现,不过我觉得这种方式有点low,谁可以告知高级一点的实现方式啊,求告知~

回复内容:

3张扑克牌叠在一起显示效果如下:Android新人求教问:如何自定义ViewGroup,望大神潜入、不吝赐教。
这个布局效果可以用该RelativeLayout或FrameLayout,然后为每一个扑克牌设置margin就能实现,不过我觉得这种方式有点low,谁可以告知高级一点的实现方式啊,求告知~

除了你说的那种,我们还可以用ViewGroup实现。不过在定制ViewGroup之前,我们需要先理解一些定义。
Android绘制视图的方式。“绘制布局由两个遍历过程组成:测量过程和布局过程。测量过程由measure(int, int)方法完成,该方法从上到下遍历视图树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当measure方法遍历结束,每个视图都保存了各自的尺寸信息。第二个过程由 layout(int,int,int,int)方法完成,该方法也是由上而下遍历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所有子视图的位置信息。”
简而言之,第一步是测量ViewGroup的宽度和高度,在onMeasure()方法中完成,ViewGroup遍历所有子视图计算出它的大小。第二步是根据第一步获取的尺寸去布局所有子视图,在onLayout()中完成。

创建CascadeLayout
终于到了定制ViewGroup的阶段了。假设我们已经定制了一个CascadeLayout的容器,我们会这样使用它。
1. 2.      
3.    xmlns:android="http://schemas.android.com/apk/res/android"  
4.    android:layout_width="fill_parent"  
5.    android:layout_height="fill_parent" >  
6.  
7.     8.        android:layout_width="fill_parent"  
9.        android:layout_height="fill_parent"  
10.          
11.        cascade:horizontal_spacing="30dp"  
12.        cascade:vertical_spacing="20dp" >  
13.  
14.         15.            android:layout_width="100dp"  
16.            android:layout_height="150dp"  
17.            android:background="#FF0000" />  
18.  
19.         20.            android:layout_width="100dp"  
21.            android:layout_height="150dp"  
22.            android:background="#00FF00" />  
23.  
24.         25.            android:layout_width="100dp"  
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.    10dp  
3.    10dp  
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. 2.    android:layout_width="fill_parent"  
3.    android:layout_height="fill_parent"  
4.    cascade:horizontal_spacing="30dp"  
5.    cascade:vertical_spacing="20dp" >  
6.  
7.      
8.     9.        android:layout_width="100dp"  
10.        android:layout_height="150dp"  
11.        cascade:layout_vertical_spacing="90dp"  
12.        android:background="#FF0000" />  
13.  
14.     15.        android:layout_width="100dp"  
16.        android:layout_height="150dp"  
17.        android:background="#00FF00" />  
18.  
19.     20.        android:layout_width="100dp"  
21.        android:layout_height="150dp"  
22.        android:background="#0000FF" />  
23.
  

其实你只需要搜索“创建定制的ViewGroup”就能找到正确的答案了。
right answer