问题:怎样创建一个例如以下图所看到的的布局?



               图1

(原文地址:http://blog.csdn.net/vector_yi/article/details/24415537)

 你可能会说,利用RelativeLayout和margins就能够实现。的确,例如以下XML代码能够简单地构建一个类似的布局:

<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>

效果如图2:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmVjdG9yX3lp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

             图2

可是当遇到复杂、要求可变的类似布局时,利用margins可能就会显得操作非常繁杂。
在此,我们来看还有一种创建类似上图布局的方式---自己定义ViewGroup
优点有下面几点:
  • 当你将这个布局应用到不同Activity中时更加easy维护
  • 能够利用自己定义属性来自己定义ViewGroup中的每一个子View
  • 更加简洁可读的XML文件内容
  • 假设须要改变margin的时候。不须要手动的去计算每一个子View的margin
一、理解Android绘制一个View的步骤
     关于绘制View的步骤。能够參见Android官方文档:http://developer.android.com/guide/topics/ui/how-android-draws.html
        在此,我们重点来关注ViewGroup的绘制过程:
          1.处理ViewGroup的width和height.
             处理width及height的操作在onMeasure()方法中进行,在此方法内。ViewGroup会依据它的子View来计算自身所占用的布局空间。
          2.布局到页面上
             这点操作在onLayout()方法中进行,在此方法中,ViewGroup会依据从onMeasure()中得到的信息将其每个子View绘制出来。


二、构建CascadeLayout类
         首先在XML布局文件里加入CascadeLayout:
<FrameLayout
<!--自己定义命名空间,以便在下文中使用自己定义的属性-->
xmlns:cascade ="http://schemas.android.com/apk/res/com.manning.androidhacks.hack003"
xmlns:android= "http://schemas.android.com/apk/res/android"
android:layout_width= "fill_parent"
android:layout_height= "fill_parent" > <com.manning.androidhacks.hack003.view.CascadeLayout
android:layout_width ="fill_parent"
android:layout_height ="fill_parent"
cascade:horizontal_spacing ="30dp"<!--由于前面加入了cascade命名空间,所以此处能够使用自己定义属性-->
cascade:vertical_spacing ="20dp" > <View
android:layout_width ="100dp"
android:layout_height ="150dp"
cascade:layout_vertical_spacing ="90dp"<!--为子View加入的自己定义属性,将在本文第三部分用到-->
android:background ="#FF0000" /> <View
android:layout_width ="100dp"
android:layout_height ="150dp"
android:background ="#00FF00" /> <View
android:layout_width ="100dp"
android:layout_height ="150dp"
android:background ="#0000FF" />
</com.manning.androidhacks.hack003.view.CascadeLayout> </FrameLayout>
要使用这些自己定义的属性,我们必需要定义它。

     在res/values目录下创建一个attrs.xml文件:
<? xml version ="1.0" encoding= "utf-8" ?>
<resources>
<declare-styleable name= "CascadeLayout" >
<attr name= "horizontal_spacing" format = "dimension" />
<attr name= "vertical_spacing" format = "dimension" />
</declare-styleable>
</resources>
然后。当我们在创建CascadeLayout且没有为其指定horizontal_spacing与vertical_spacing时,须要有一个默认值。

我们将这个默认值预先定义好并存放在res/values目录下的dimens.xml中:
<?

xml version ="1.0" encoding= "utf-8" ?

>
<resources>
<dimen name= "cascade_horizontal_spacing" >10dp</dimen>
<dimen name= "cascade_vertical_spacing" >10dp</dimen>
</resources>
最后,我们须要创建一个名为CascadeLayout的Java类。它继承了ViewGroup并重写了onMeasure()与OnLayout()方法。
1.CascadeLayout的构造函数
public CascadeLayout (Context context, AttributeSet attrs) {
super( context, attrs); TypedArray a = context .obtainStyledAttributes (attrs ,
R. styleable. CascadeLayout ); try {
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 ();
}
2.构建自己定义的LayoutParams类
     LayoutParams类将作为CascadeLayout的内部类存在,它将存储每一个子View的x。y坐标。定义例如以下:
public static class LayoutParams extends ViewGroup .LayoutParams {
int x;
int y; public LayoutParams( Context context , AttributeSet attrs) {
super (context , attrs );
} public LayoutParams( int w , int h ) {
super (w , h );
} }
3.重写onMeasure()方法
     onMeasure()方法将是CascadeLayout类中最关键的部分,这种方法不仅计算整个ViewGroup所占用的布局空间。还将计算出每一个子View所占用的布局空间。
@Override
protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
int width = 0;
int height = getPaddingTop (); final int count = getChildCount ();
for ( int i = 0; i < count; i++) {
View child = getChildAt (i );
measureChild (child , widthMeasureSpec , heightMeasureSpec );
LayoutParams lp = (LayoutParams ) child .getLayoutParams ();
width = getPaddingLeft () + mHorizontalSpacing * i; lp .x = width;
lp .y = height; width += child .getMeasuredWidth ();
height += mVerticalSpacing ;
} width += getPaddingRight ();
height += getChildAt (getChildCount () - 1). getMeasuredHeight ()
+ getPaddingBottom (); setMeasuredDimension ( resolveSize( width, widthMeasureSpec ),
resolveSize( height, heightMeasureSpec ));
}
4.最后一步,重写onLayout()方法
     代码非常easy,就是让每一个子View都调用layout()方法。
@Override
protected void onLayout (boolean changed, int l , int t , int r , int b ) { final 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 ());
}
}
至此,就利用自己定义的ViewGroup创建了一个和图2一样效果的布局页面。


三、为子View加入自己定义属性

既然费了这么大劲,怎么可能就和之前几行XML代码效果一样?
以下,我们就来为CascadeLayout中的子View加入自己定义属性:
     首先,在之前创建的attrs.xml中加入例如以下代码:
<declare-styleable name="CascadeLayout_LayoutParams">
<attr name="layout_vertical_spacing" format="dimension" />
</declare-styleable>
由于这个新加入的属性是以 layout_ 开头的。所以它会被加入到LayoutParams中去。
我们能够在之前自己定义的内部类LayoutParams中的构造函数中读取到这个属性,将第一个构造函数改为:
public LayoutParams (Context context, AttributeSet attrs) {
super (context , attrs ); TypedArray a = context .obtainStyledAttributes (attrs ,
R. styleable. CascadeLayout_LayoutParams );
try {
verticalSpacing = a
.getDimensionPixelSize (
R .styleable .CascadeLayout_LayoutParams_layout_vertical_spacing ,
-1 );
} finally {
a .recycle ();
}
}
既然加入了新的自己定义属性。就必须在onMeasure()方法中对其加以处理:
@Override
protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
int width = getPaddingLeft ();
int height = getPaddingTop ();
int verticalSpacing ; final 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; lp .x = width;
lp .y = height; if (lp .verticalSpacing >= 0 ) {
verticalSpacing = lp .verticalSpacing ;
} width += child .getMeasuredWidth ();
height += verticalSpacing ;
} width += getPaddingRight ();
height += getChildAt (getChildCount () - 1). getMeasuredHeight ()
+ getPaddingBottom (); setMeasuredDimension ( resolveSize( width, widthMeasureSpec ),
resolveSize( height, heightMeasureSpec ));
}

最后附上完整的CascadeLayout代码:
package com.manning.androidhacks.hack003.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup; import com.manning.androidhacks.hack003.R; public class CascadeLayout extends ViewGroup { private int mHorizontalSpacing;
private int mVerticalSpacing; public CascadeLayout(Context context, AttributeSet attrs) {
super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout); try {
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) {
int width = getPaddingLeft();
int height = getPaddingTop();
int verticalSpacing; final 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; lp.x = width;
lp.y = height; if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
} width += child.getMeasuredWidth();
height += verticalSpacing;
} width += getPaddingRight();
height += getChildAt(getChildCount() - 1).getMeasuredHeight()
+ getPaddingBottom(); setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { final 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());
}
} @Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
} @Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
} @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
} @Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
} public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
public int verticalSpacing; public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs); TypedArray a = context.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);
} }
}

project文件夹结构:





(原文地址:http://blog.csdn.net/vector_yi/article/details/24415537)


50个Android开发技巧(03 自己定义ViewGroup)的更多相关文章

  1. 50个android开发技巧

    50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html

  2. 50一个Android开发技巧(01 利用好layout_weight属性)

    问题:如何将一个Button放置在布局的中间,并设置其宽度parent的50%? 分析:问题想要达到的效果应该是这样: (原文地址:http://blog.csdn.net/vector_yi/art ...

  3. 50个Android开发技巧(02 延迟载入和避免反复渲染视图)

    当你在Application中创建复杂的布局时.页面的渲染过程也变得更加缓慢. 此时,我们须要利用 <include />标签(避免反复渲染)和 ViewStub类(延迟载入)来优化我们的 ...

  4. 50个Android开发技巧(24 处理ListView数据为空的情况)

         在移动平台上为用户展示数据的一个经常用法是将数据填充进一个List内,而此时须要注意的一点就是: 原文地址:(http://blog.csdn.net/vector_yi/article/d ...

  5. 50个Android开发技巧(11 为文字加入特效)

    问题:怎样构建一个模拟LED数字时钟的页面?效果例如以下图所看到的: (原文地址:http://blog.csdn.net/vector_yi/article/details/24460227) 分析 ...

  6. 50个Android开发技巧(10 为TextView加入样式)

    首先来看一个控件的例子: (原文地址:http://blog.csdn.net/vector_yi/article/details/24428085) 手机上类似这种场景你一定已经见过非常多次了,但有 ...

  7. 50个Android开发技巧(12 为控件加入圆角边框)

    控件的圆角边框能够使你的App看起来更美观,事实上实现起来也非常easy. (原文地址:http://blog.csdn.net/vector_yi/article/details/24463025) ...

  8. 50个Android开发技巧(09 避免用EditText对日期进行验证)

    我们都知道,在表单中对数据进行验证不但无聊并且easy出错. (原文地址:http://blog.csdn.net/vector_yi/article/details/24424713) 想象一下,一 ...

  9. Android开发技巧——大图裁剪

    本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...

随机推荐

  1. SQL Server FOR XML PATH 语句的应用---列转行

    经常在论坛看到高手使用了 for xml path,由于是搜索一下,记录了详细的使用方法.在SQL Server中利用 FOR XML PATH 语句能够把查询的数据生成XML数据,下面是它的一些应用 ...

  2. Eclipse中Editor开启Auto-completion

    Java Editor .abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ Java Script Editor 现在Eclipse限制使用最多 ...

  3. java资料——线性表(转)

    线性表 线性表(亦作顺序表)是最基本.最简单.也是最常用的一种数据结构.线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的.线性表的逻辑结构简单, ...

  4. 机器学习:K-Means聚类算法

    本文来自同步博客. 前面几篇文章介绍了回归或分类的几个算法,它们的共同点是训练数据包含了输出结果,要求算法能够通过训练数据掌握规律,用于预测新输入数据的输出值.因此,回归算法或分类算法被称之为监督学习 ...

  5. mongodb查询之从多种分类中获取各分类最新一条记录

    mongodb查询之从多种分类中获取各分类最新一条记录 2017年04月06日 13:02:47 monkey_four 阅读数:6707更多 个人分类: MongoDBJavaScript   文章 ...

  6. kbengine + cocos2d-js-demo理解

    KBEngine 是国内开源的游戏服务器引擎,据说参考了 Bigworld 的架构:网上能找到的开源游戏服务器引擎很少,网易的 Pomelo 是用 Node.js 来实现的,现在还是觉得 C/C++ ...

  7. jquery实现简单瀑布流代码

    测试环境:ie8 ff13.0.1  chrome22 可以将分页获取的内容依次填入四个div中,瀑布流的分页可以以多页(比如5页)为单位二次分页,这样可以减少后台算法的复杂度 <!DOCTYP ...

  8. C++对析构函数的误解

    C++析构前言 析构函数在什么时候会自动被调用,在什么时候需要手动来调用,真不好意思说偶学过C++…今日特此拨乱反正. C++析构误解正文 对象在构造的时候系统会分配内存资源,对一些数据成员进行初始化 ...

  9. 利用VBA宏批量解决Word中图片大小、居中设置

    需求:经常阅读网上的研报(没钱买排版漂亮的高质量研报),有些需要保存的复制下来到word里,图片很大都超出word的边界了,也没有居中,手工一张张调整不现实,上百页的研报,几十张图片. 解决方案:利用 ...

  10. 关于Unity的入门游戏飞机大战的开发(上)

    每个组件都是一个类的实例,要获得某个组件,要先创建一个同类型的组件类实例,然后把实例传引用过去,就可以对想要的组件实例进行操作. 做游戏一般创建一个逻辑节点,里面只管逻辑,再创建一个动画节点,里面有好 ...