ホームページ  >  記事  >  バックエンド開発  >  Android新人求教问:如何自定义ViewGroup,望大神潜入、不吝赐教。

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

WBOY
WBOYオリジナル
2016-06-06 20:43:16999ブラウズ

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

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。