一、原理分析

        在Android中除了控件之外还有布局控件或者容器控件,比如LinearLayout、FrameLayout等,它们通常不会实现具体的功能,主要负责布局和管理所包含的子控件。Android系统提供的布局控件都有各自的使用规则,需要根据实际的需要选择合适的布局控件。

        如果系统提供的布局控件无法满足实际的布局要求,可以通过扩展ViewGroup类来实现自己的布局控件。在扩展ViewGroup类时一般需要覆盖并实现如下三个重要的方法:

        onMeasure():计算控件所需要的区域空间。
        onLayout():布局子控件。
        dispatchDraw():绘制布局控件,需要特别注意:对于自定义控件需要覆盖onDraw方法,而自定义布局控件需要覆盖dispatchDraw方法。

二、示例分析

        下面通过一个Demo来说明,如何通过扩展ViewGroup类实现自定义布局控件。对于Android中的线性布局控件LinearLayout,只能横向或者纵向排列子控件,而且横向布局时不能自动换行。实际上,通过扩展ViewGroup类,通过一些简单的算法就能够实现支持自动换行的布局控件。

        onMeasure、onLayout、dispatchDraw三个方法在Demo中的作用总结如下:

        onMeasure:计算控件及其子控件所占区域
        onLayout:控制子控件的换行
        dispatchDraw:为布局控件添加边框

        JAVA代码如下:

001 package com.devdiv.test.view_test_viewgroup;
002  
003 import android.content.Context;
004 import android.content.res.TypedArray;
005 import android.graphics.Canvas;
006 import android.graphics.Color;
007 import android.graphics.Paint;
008 import android.graphics.Rect;
009 import android.util.AttributeSet;
010 import android.view.View;
011 import android.view.ViewGroup;
012 import android.view.View.MeasureSpec;
013  
014 public class FixedGridLayout extends ViewGroup {
015          
016         private int mCellWidth;
017         private int mCellHeight;
018  
019         public FixedGridLayout(Context context) {
020                 super(context);
021                 // TODO Auto-generated constructor stub
022         }
023  
024         public FixedGridLayout(Context context, AttributeSet attrs) {
025                 super(context, attrs);
026                 // TODO Auto-generated constructor stub
027                  
028                 //初始化单元格宽和高
029                 mCellWidth = 60;
030                 mCellHeight = 60;
031                  
032         }
033          
034         //设置单元格宽度
035         public void setCellWidth(int w) {
036                 mCellWidth = w;
037                  
038                 //Call this when something has changed which has invalidated the layout of this view.
039         //This will schedule a layout pass of the view tree
040                 requestLayout();
041         }
042          
043         //设置单元格高度
044         public void setCellHeight(int h) {
045                 mCellHeight = h;
046                 requestLayout();
047         }
048          
049          
050         @Override
051         protected void dispatchDraw(Canvas canvas) {
052                 // TODO Auto-generated method stub
053                  
054                 //获取布局控件宽高
055                 int width = getWidth();
056                 int height = getHeight();
057                 //创建画笔
058                 Paint mPaint = new Paint();
059                 //设置画笔的各个属性
060                 mPaint.setColor(Color.BLUE);
061                 mPaint.setStyle(Paint.Style.STROKE);
062                 mPaint.setStrokeWidth(10);
063                 mPaint.setAntiAlias(true);
064                  
065                 //创建矩形框
066                 Rect mRect = new Rect(00, width, height);
067                 //绘制边框
068                 canvas.drawRect(mRect, mPaint);
069                  
070                 //最后必须调用父类的方法
071                 super.dispatchDraw(canvas);
072         }
073  
074  
075         @Override
076         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
077                 // TODO Auto-generated method stub
078                  
079                 //创建测量参数
080                 int cellWidthSpec = MeasureSpec.makeMeasureSpec(mCellWidth, MeasureSpec.AT_MOST);
081                 int cellHeightSpec = MeasureSpec.makeMeasureSpec(mCellHeight, MeasureSpec.AT_MOST);
082                  
083                 //记录ViewGroup中Child的总个数
084                 int count = getChildCount();
085                  
086                 //设置子空间Child的宽高
087                 for (int i = 0; i < count; i++) {
088                         View childView = getChildAt(i);
089                          /*
090              * This is called to find out how big a view should be.
091              * The parent supplies constraint information in the width and height parameters.
092              * The actual mesurement work of a view is performed in onMeasure(int, int),
093              * called by this method.
094              * Therefore, only onMeasure(int, int) can and must be overriden by subclasses.
095              */
096                         childView.measure(cellWidthSpec, cellHeightSpec);
097                 }
098                  
099                 //设置容器控件所占区域大小
100                 //注意setMeasuredDimension和resolveSize的用法
101                 setMeasuredDimension(resolveSize(mCellWidth * count, widthMeasureSpec), resolveSize(mCellHeight * count, heightMeasureSpec));
102                 //setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
103                  
104                 //不需要调用父类的方法
105                 //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
106         }
107  
108  
109         @Override
110         protected void onLayout(boolean changed, int l, int t, int r, int b) {
111                 // TODO Auto-generated method stub
112                  
113                 int cellWidth = mCellWidth;
114                 int cellHeight = mCellHeight;
115                 int columns = (r - l) / cellWidth;
116                  
117                 if(columns < 0) {
118                         columns = 1;
119                 }
120                  
121                 int x = 0;
122                 int y = 0;
123                 int i = 0;
124                 int count = getChildCount();
125                  
126                 for (int j = 0; j < count; j++) {
127                         final View childView = getChildAt(j);
128                         //获取子控件Child的宽高
129                         int w = childView.getMeasuredWidth();
130                         int h = childView.getMeasuredHeight();
131                         //计算子控件的顶点坐标
132                         int left = x + ((cellWidth - w)/2);
133                         int top = y + ((cellHeight - h)/2);
134                         //int left = x;
135                         //int top = y;
136                         //布局子控件
137                         childView.layout(left, top, left + w, top + h);
138                          
139                         if(i >= (columns - 1)) {
140                                 i = 0;
141                                 x = 0;
142                                 y += cellHeight;
143                         else {
144                                 i++;
145                                 x += cellWidth;
146                                                  
147                         }
148                          
149                 }
150  
151         }
152  
153 }

        完成了FixedGridLayout类之后,就可以在xml布局文件中使用它了,对于自定义布局控件也是一样,需要使用完整的类名。FixedGridLayout控件能够自动排列子控件,所以不需要设置太多的属性,只要按顺序添加子控件即可,下面是完整的xml代码:

01 <?xml version="1.0" encoding="utf-8"?>
02 <com.devdiv.test.view_test_viewgroup.FixedGridLayout
03     xmlns:android="http://schemas.android.com/apk/res/android"
04     android:id="@+id/mlayout"
05     android:layout_width="fill_parent"
06     android:layout_height="fill_parent"
07     >
08      
09     <ImageView
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content"
12         android:src="@drawable/bugdroid" />
13      
14     <ImageView
15         android:layout_width="wrap_content"
16         android:layout_height="wrap_content"
17         android:src="@drawable/bugdroid" />
18      
19     <ImageView
20         android:layout_width="wrap_content"
21         android:layout_height="wrap_content"
22         android:src="@drawable/bugdroid" />
23      
24     <ImageView
25         android:layout_width="wrap_content"
26         android:layout_height="wrap_content"
27         android:src="@drawable/bugdroid" />
28      
29     <ImageView
30         android:layout_width="wrap_content"
31         android:layout_height="wrap_content"
32         android:src="@drawable/bugdroid" />
33      
34     <ImageView
35         android:layout_width="wrap_content"
36         android:layout_height="wrap_content"
37         android:src="@drawable/bugdroid" />
38  
39  
40 </com.devdiv.test.view_test_viewgroup.FixedGridLayout>

        其中,构造函数中完成了mCellWidth和mCellHeight的初始化,代码如下:

1         public FixedGridLayout(Context context, AttributeSet attrs) {
2                 super(context, attrs);
3                 // TODO Auto-generated constructor stub
4                  
5                 //初始化单元格宽和高
6                 mCellWidth = 60;
7                 mCellHeight = 60;
8                  
9         }

        也可以对mCellWidth和mCellHeight进行不同的设置,代码如下:

1         FixedGridLayout mFixedGridLayout = (FixedGridLayout) findViewById(R.id.mlayout);
2         mFixedGridLayout.setCellWidth(120);
3         mFixedGridLayout.setCellHeight(120);

三、运行效果


QQ截图20120605122844.png


图5-4  Demo运行效果图(mCellWidth=mCellHeight=60)

QQ截图20120605122930.png


图5-5  Demo运行效果图(mCellWidth=mCellHeight=120)


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐