创建定制的ViewGroup
来源:互联网 发布:linux最新内核编译 编辑:程序博客网 时间:2024/05/17 08:52
如需建如下的布局,我们应该如何创建这样的布局呢?
虽然使用margin属性便足以实现这种布局,XML布局文件源码如下:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <View android:layout_width="100dp" android:layout_height="150dp" android:background="#FF0000"/> <View android:layout_width="100dp" android:layout_height="150dp" android:layout_marginLeft="30dp" android:layout_marginTop="20dp" android:background="#00FF00"/> <View android:layout_width="100dp" android:layout_height="150dp" android:layout_marginLeft="60dp" android:layout_marginTop="40dp" android:background="#0000FF"/></RelativeLayout>
但我们可以通过创建自定义的ViewGroup来实现上述功能。该方法相对于在XML文件中手工指定margin值有如下优点:
1、在不同Activity中复用该视图时,更易维护。
2、开发者可以使用自定义属性来定制ViewGroup中子视图的位置。
3、布局文件更简明,更容易理解。
4、如果需要修改margin,不必重新手动计算每个子视图的margin。
理解Android绘制视图的方式
Android如何绘制一个布局:
”绘制布局由两个遍历过程组成:测量过程和布局过程。测量过程由measure(int, int)方法完成,该方法从上到下遍历视图树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当measure方法遍历结束,每个视图都保存了各自的尺寸信息。第二个过程由layout(int, int, int, int)方法完成,该方法也是从上到下遍历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所有子视图的位置信息“。
为了理解这个概念,下面分析ViewGroup的绘制过程。第一步是测量ViewGroup的宽度和高度。在onMeasure()方法中完成这步操作。在该方法中,ViewGroup通过遍历所有子视图计算出它的大小。最后一步操作,在onLayout()方法中完成,在该方法中,ViewGroup利用上一步计算出的测量信息,布局所有子视图。
创建自定义CascadeLayout
首先要定义CascadeLayout的定制属性,需要在res/values目录下创建一个属性文件attrs.xml,该文件的内容如下:
<?xml version="1.0" encoding="utf-8"?><resources> <!--为CascadeLayout视图添加自定义属性。 declare-styleable中的name属性值必为自定义控件的类名(CascadeLayout),不然在布局文件中将无法使自定义控件的定制属性--> <declare-styleable name="CascadeLayout"> <attr name="horizontal_spacing" format="dimension"/> <attr name="vertical_spacing" format="dimension"/> </declare-styleable> <!--为CascadeLayout的子视图添加自定义属性。添加为特定子视图重写(override)垂直间距的方法,name属性值必为父控件类名_父控件的LayoutParams内部类(CascadeLayout_LayoutParams)--> <declare-styleable name="CascadeLayout_LayoutParams"> <!--因为属性名的前缀是layout_,没有包含一个视图属性,因此该属性会被添加到LayoutParams的属性表中--> <attr name="layout_vertical_spacing" format="dimension"/> </declare-styleable></resources>
同时还需要指定水平间距和垂直间距的默认值,以便在未指定这些值时使用。把这些默认值保存在dimens.xml文件中,该文件同样位于res/values文件夹下。dimens.xml文件如下:
<resources> <dimen name="cascade_horizontal_spacing">10dp</dimen> <dimen name="cascade_vertical_spacing">10dp</dimen></resources>
创建CascadeLayout类
package com.example.huangfei.demo;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;/** * Created by huangfeihong on 2015/10/25. */public class CascadeLayout extends ViewGroup { private int mHorizontalSpacing;//水平间距 private int mVerticalSpacing;//垂直间距 /** * 当通过XML文件创建该视图的实例时会调用该构造函数 */ public CascadeLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeLayout); try { /** * mHorizontalSpacing和mVerticalSpacing由自定义属性中获取,如果其值未指定,就使用默认值 */ mHorizontalSpacing = a.getDimensionPixelSize(R.styleable.CascadeLayout_horizontal_spacing, getResources().getDimensionPixelSize(R.dimen.cascade_horizontal_spacing)); mVerticalSpacing = a.getDimensionPixelSize(R.styleable.CascadeLayout_vertical_spacing, getResources().getDimensionPixelSize(R.dimen.cascade_vertical_spacing)); } finally { a.recycle(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //使用宽和高计算布局的最终大小以及子视图的x与y轴位置 int width = getPaddingLeft(); int height = getPaddingTop(); int verticalSpacing;//子视图垂直间距 int count = getChildCount(); for (int i = 0; i < count; i++) { verticalSpacing = mVerticalSpacing; View child = getChildAt(i); //令每个子视图测量自身 measureChild(child, widthMeasureSpec, heightMeasureSpec); LayoutParams lp = (LayoutParams) child.getLayoutParams(); width = getPaddingLeft() + mHorizontalSpacing * i; //在LayoutParams中保存每个子视图的x和y坐标 lp.x = width; lp.y = height; if(lp.verticalSpacing >= 0){ verticalSpacing = lp.verticalSpacing; } width += child.getMeasuredWidth(); height += verticalSpacing; } width += getPaddingRight(); height += getChildAt(count - 1).getMeasuredHeight() + getPaddingBottom(); //使用计算所得的宽和高设置整个布局的测量尺寸 setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); } /** * 该方法以onMeasure()计算出的值为参数循环调用子View的layout()方法 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight()); } } /** * 要使自新定义的CascadeLayout.LayoutParams类,必须重写以下四个方法。 * 这些方法的代码在不同ViewGroup之间往往是相同的。 */ @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p.width, p.height); } /** * 该类用于保存每个子视图的x、y轴位置 */ public static class LayoutParams extends ViewGroup.LayoutParams { int x; int y; int verticalSpacing;//子视图自定义垂直间距 public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CascadeLayout_LayoutParams); try { verticalSpacing = a.getDimensionPixelSize( R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing, -1); } finally { a.recycle(); } } public LayoutParams(int w, int h) { super(w, h); } }}
创建Activity的布局文件
<?xml version="1.0" encoding="utf-8"?><!--在XML中使用自定义属性时指定自定义命名空间--><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cascade="http://schemas.android.com/apk/res-auto" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!--通过cascade命名空间,就可以使用其自定义属性--> <com.example.huangfei.demo.CascadeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" cascade:horizontal_spacing="50dp" cascade:vertical_spacing="20dp"> <!--父视图添加自定义属性--> <View android:layout_width="100dp" android:layout_height="150dp" android:background="#FF0000" cascade:layout_vertical_spacing="90dp"/> <!--子视图添加自定义属性--> <View android:layout_width="100dp" android:layout_height="150dp" android:background="#00FF00" cascade:layout_vertical_spacing="50dp"/> <View android:layout_width="100dp" android:layout_height="150dp" android:background="#0000FF"/> </com.example.huangfei.demo.CascadeLayout></FrameLayout>
代码地址
- 创建定制的ViewGroup
- Hack3 - 创建定制的ViewGroup
- android 开发技巧(3)--创建定制的 ViewGroup
- 创建定制的组件
- python 创建定制的序列
- 神奇的 ViewDragHelper,让你轻松定制拥有拖拽能力的 ViewGroup
- Hack3-创建自定义ViewGroup
- 如何创建定制的 BlackBerry UI Field
- 如何创建定制的BlackBerry UI Field
- ViewGroup 的
- C#中关于创建定制的泛型类的例子
- 为snapcraft创建一个简单的定制的plugin
- 创建定制枚举器
- 用J2SE 5.0创建定制的泛型集合
- 用 jsp 定制标签创建超连接的方法(一)
- 用jsp定制标签创建超连接的方法(二)
- 星战online的可定制角色创建技术
- CakePHP: 使用Flickr创建定制的相册和增值服务
- LDAP 认证服务可用性监测
- Foundation 之 NSDictionary和NSMutableDictionary
- 关于服务器端跳转和客户端跳转
- Mysql cookbook 2
- 安卓的广播机制学习
- 创建定制的ViewGroup
- 75道逻辑思维训练题
- CentOS 6.5 中文输入法没有候选框解决
- Android 中style的使用
- 软件测试5--软件测试的重要概念
- poj 1158 TRAFFIC LIGHTS spfa求最短路
- Ant最完整的build.xml解释
- Spring官网下载dist.zip的几种方法
- xx项目机器人底层运动控制方案