Android深入浅出自定义控件(二)
来源:互联网 发布:vb计算圆的面积和周长 编辑:程序博客网 时间:2024/06/05 12:38
在我的上篇博文Android深入迁出自定义控件(一)中介绍了如何自定义View控件,本篇博文主要介绍如何自定义ViewGroup
什么是ViewGroup?
在Android的树状结构图中,ViewGroup类衍生出我们所熟悉的LinearLayout、RelativeLayout等布局:简单来说,ViewGroup其实就相当于所有布局的父亲,所以我们可以通过自定义ViewGroup类实现千变万化的布局。
自定义ViewGroup主要分为以下几个步骤:
1.创建自定义ViewGroup类,继承于ViewGroup类,重写ViewGroup的三个构造方法2.重写onMeasure方法,设置好ViewGroup及其子View在界面上所显示的大小
3.添加参数
4.重写onLayout方法,设置好子View的布局,这个方法也是最为关键的,它决定了你所自定义的布局的特性
1.创建自定义ViewGroup类,继承于ViewGroup类,重写ViewGroup的三个构造方法
public class FlowLayoutextends ViewGroup{public FlowLayout(Context context) {// TODO Auto-generated constructor stubthis(context,null);}public FlowLayout(Context context, AttributeSet attrs) {// TODO Auto-generated constructor stubthis(context,attrs,0);}public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stub}}
2.重写onMeasure方法,设置好ViewGroup及其子View在界面上所显示的大小
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubmeasureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}
对这个方法的使用见下文,此处记得添加measureChildren(widthMeasureSpec, heightMeasureSpec);表示由系统自己测量每个子View的大小
3.添加参数,比如我们想要让子View之间能有间距,则需要手动创建一个内部参数类
public static class FlowLayoutParams extends ViewGroup.MarginLayoutParams{public FlowLayoutParams(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}}
4.重写onLayout方法,设置好子View的布局,这个方法也是最为关键的,它决定了你所自定义的布局的特性
@Overrideprotected void onLayout(boolean arg0, int left, int top, int right, int bottom) {// TODO Auto-generated method stub//获得FlowLayout所测量出来的宽度int mViewGroupWidth = getMeasuredWidth();/*** paintX:绘制每个View时的光标起点的横坐标* paintY:绘制每个View时的光标起点的纵坐标*/int paintX = left;int paintY = top;//用于记录上一行的最大高度int maxlineHeight = 0;int childCount = getChildCount();for(int i=0; i<childCount; i++){View childView = getChildAt(i);//获得每个子View的margin参数FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();//获得每个子View所测量出来的宽度和高度int childViewWidth = childView.getMeasuredWidth();int childViewHeight = childView.getMeasuredHeight();//如果绘制的起点横坐标+左间距+子View的宽度+右间距比FlowLayout的宽度还大,就需要进行换行if(paintX + childViewWidth + params.leftMargin + params.rightMargin> mViewGroupWidth){//绘制的起点的横坐标重新移回FlowLayout的横坐标paintX = left;//绘制的起点的纵坐标要向下移动一行的高度paintY = paintY + maxlineHeight + params.topMargin + params.bottomMargin;maxlineHeight = 0;}maxlineHeight = Math.max(childViewHeight, maxlineHeight);childView.layout(paintX+params.leftMargin, paintY+params.topMargin, paintX+childViewWidth+params.leftMargin, paintY+childViewHeight+params.topMargin);//每绘制一次,起点光标就要向右移动一次paintX = paintX + childViewWidth + params.leftMargin + params.rightMargin;}}
解析:在onLayout方法中,我们对每个子View进行遍历并设置好它们应该在的位置,比如是LinearLayout的话,在onLayout中系统会规定它的子View只能按着横向或者竖向排列下去,也就是说,onLayout里子View的排布是按着我们自己的想法来决定,到底这个自定义布局会有怎样的特性?
此处我们demo是自定义FlowLayout,也就是每一行都向右排列下去,直到这一行不够容纳,则子View自动换行,进入下一行,依此类推,从而实现流式布局,为了实现这样的效果,最关键的应该是在换行的时候,需要实现让我们的子View能够判断到底换不换行,代码思路如下:
1.首先需要记录FlowLayout的宽度, 作为每一行的宽度上限:
int mViewGroupWidth = getMeasuredWidth();
2.每次绘制一个子View,是通过View.layout()方法来进行,而layout方法需要提供4个参数,即(绘制的起点横坐标,绘制的起点纵坐标,子View的宽度,子View的高度),而每一个子View的绘制起点肯定不一样,所以需要定义两个变量来记录:paintX,paintY:
/** * paintX:绘制每个View时的光标起点的横坐标 * paintY:绘制每个View时的光标起点的纵坐标 */int paintX = left;int paintY = top;
3.通过for循环,遍历得到每个子View,由于要让子View之间能够有间距,所以还需要定义一个margin参数提供给子View:
FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();
以及获得每个子View的宽高:
int childViewWidth = childView.getMeasuredWidth();int childViewHeight = childView.getMeasuredHeight();
4.判断如果再添加一个子View,需不需要换行,所以需要将这个View的宽度和当前行的宽度相加,与FlowLayout的宽度(即上限)进行对比,如果超过上限,就进行换行操作:
//绘制的起点的横坐标重新移回FlowLayout的横坐标paintX = left;//绘制的起点的纵坐标要向下移动一行的高度paintY = paintY + maxlineHeight + params.topMargin + params.bottomMargin;//由于换行,所以当前行变成了下一行,最大高度自然也就置为当前这个新起行的子View的高度(因为它是下一行的第一个View)maxlineHeight = childViewHeight;
5.绘制当前子View,光标移动至下一个起点:
//每次都要计算当前行的最大高度maxlineHeight = Math.max(childViewHeight, maxlineHeight);childView.layout(paintX+params.leftMargin, paintY+params.topMargin, paintX+childViewWidth+params.leftMargin, paintY+childViewHeight+params.topMargin);//每绘制一次,起点光标就要向右移动一次paintX = paintX + childViewWidth + params.leftMargin + params.rightMargin;
至此,一个基本的FlowLayout布局定义完成,接下来就只需要在布局文件中来使用它:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" > <com.example.view.FlowLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="全部" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="体育" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="连续剧" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="综艺" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="电影" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="搞笑" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="儿童教育" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="技术教程" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="音乐频道" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="赛事直播" /> </com.example.view.FlowLayout></RelativeLayout>
运行:
可以看到达到了我们所要的效果,但这里我们设置的FlowLayout的大小都是fill_parent,如果改为wrap_content会怎样?
将FlowLayout的layout_height设置为wrap_content,虽然屏幕上仍然呈现的是一样的效果,可是在预览窗口中点击FlowLayout,可以看到其实FlowLayout依旧是fill_parent的大小,而不是贴着子View的边缘,如图:
这不是我们想要的效果,正确的做法应该是:设置为wrap_content时,FlowLayout的边界是紧紧贴着子View的边缘的,所以我们应该修改onMeasure方法:
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stub//measureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);//得到FlowLayout的模式和宽高int widthMode = MeasureSpec.getMode(widthMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);//记录wrap_content下的最终宽度和高度int width = 0;int height = 0;//记录每一行的最大宽度和最大高度int lineWidth = 0;int lineHeight = 0;int childCount = getChildCount();for(int i=0;i<childCount;i++){View childView = getChildAt(i);measureChild(childView, widthMeasureSpec, heightMeasureSpec);FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();int childWidth = params.leftMargin + childView.getMeasuredWidth() + params.rightMargin;int childHeight = params.topMargin + childView.getMeasuredHeight() + params.bottomMargin;//如果是换行,则比较宽度取当前行的最大宽度和下一个子View的宽度,将最大者暂时作为FlowLayout的宽度if(lineWidth + childWidth > widthSize){width = Math.max(lineWidth, childWidth); height = height + lineHeight;lineWidth = childWidth;lineHeight = childHeight;}else // 否则累加值lineWidth,lineHeight取最大高度 { lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); } //如果是最后一个子Viewif (i == childCount - 1){ width = Math.max(width, lineWidth); height += lineHeight; } }//根据模式来决定FlowLayout的大小,如果不是EXACTLY,则说明布局文件中设置的模式应该是wrap_content/*** 如果是wrap_content,则将FlowLayout宽度和高度设置为我们计算出来的最终宽高* 如果是fill_parent或者具体数值,则将FlowLayout宽度和高度设置为一开始getMode和getSize得到的那个宽高*/setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height); }
再次查看预览窗口:
关于重写系统控件会在以后的博文中整合,希望本文能够让大家对自定义ViewGroup有所帮助。
- Android深入浅出自定义控件(二)
- Android深入浅出自定义控件(一)
- Android深入浅出自定义控件(三)
- Android 自定义控件(二)
- android 自定义控件(二)
- android 自定义控件(二)
- Android自定义控件(二)
- Android自定义控件(二)组合控件
- Android自定义控件二
- Android自定义控件<二>
- Android 自定义控件开发入门(二)
- Android 自定义控件开发入门(二)
- android自定义控件二(转载)
- Android常用自定义控件(二)
- android的自定义控件简单(二)
- Android自定义组合控件(二)
- Android自定义控件(二):提高篇
- Android--自定义控件解析(二)
- c++通过指针实现队列
- C++ opencv 读取mp4文件
- Unity3D 自发型俄罗斯大方块。
- HDU-5695-Gym Class(拓扑排序+优先队列)
- 并发
- Android深入浅出自定义控件(二)
- perl 回调函数
- 立即执行函数(IIFE)的理解与运用
- 面向对象测试
- android进程间通信--Binder
- STAMPS POJ 1010
- 【SSH】错误锦集
- Struts2中struts.xml的Action配置详解(一)===》 struts2.0中struts.xml配置文件详解 (二)==》Struts2_struts.xml配置及例程三
- 哥德巴赫猜想(c 循环+素数判断)