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



               图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. 【Codewars】7×7 摩天大楼

    介绍 链接:7×7 Skyscrapers C#答案(原因:懒,但是完全可以转成C++):bajdcc/learnstl 题目(机翻): 在7乘7格的网格中,你只想在每个广场上放置一个摩天大楼,只有一 ...

  2. GNU风格 ARM汇编语法2

    .GNU汇编程序中的标号symbol(或label) 标号只能由a-z,A-Z,-,".",_等(由点.字母.数字.下划线等组成,除局部标号外,不能以数字开头)字符组成. Symb ...

  3. Debug文件夹是什么

    debug是一个隐藏的对象,用于进行和调试相关的工作.是系统调试文件夹,用来存放系统运行过程中调试模块的日志文件,以便管理员根据这些日志文件来发现计算机所存在的问题.其中“UserMode”文件夹下存 ...

  4. c++友元函数之---一种特殊的友情

    类可以允许其他类或者函数访问它的私有成员,方法是令其他类或者函数成为它的友元.如果类想把一个函数或者类声明成它的友元,只需要增加一条以friend关键字开始的声明语句即可. 友元声明只能出现在类定义的 ...

  5. opensips安装

    主要流程见这个教程吧:感谢大佬 http://blog.csdn.net/u011026329/article/details/50821679 其中 mediaproxy的安装会出错.做如下修改 下 ...

  6. 1326: The contest(并查集+分组背包)

    http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1326 殷犇有很多队员.他们都认为自己是最强的,于是,一场比赛开始了~ 于是安叔主办了一场比赛,比赛 ...

  7. win下安装redis

    redis官方不提供win安装程序,github上有个开源项目提供. https://github.com/MSOpenTech/redis 微软开源团队维护 ## 目录 - 安装 - 启动 - 使用 ...

  8. windows 批处理-重命名

    从数字1递增批量重命名ren.bat: @echo off SETLOCAL ENABLEDELAYEDEXPANSION set /A num= FOR /F "tokens=*" ...

  9. linux搭建git服务器

    服务端配置 1.安装git 2.新建一个用户,只能用来上传代码,而不能通过ssh登录,比如git用户 adduser git chsh -s $(command -v git-shell) git 使 ...

  10. PHP并发IO编程之路

    并发IO问题一直是服务器端编程中的技术难题,从最早的同步阻塞直接Fork进程,到Worker进程池/线程池,到现在的异步IO.协程.PHP程序员因为有强大的LAMP框架,对这类底层方面的知识知之甚少, ...