自定义View包括很多种,上一次随笔中的那一种是完全继承自View,这次写的这个小Demo是继承自ViewGroup的,主要是将自定义View继承自ViewGroup的这个流程来梳理一下,这次的Demo中自定义了一个布局的效果,并且这个自定义布局中包含布局自己的属性,布局中的控件也包含只属于这个布局才具有的自定义属性(类似于layout_weight只存在于LinearLayout中,只有LinearLayout中的控件可以使用一样)。话不多说,先看效果图:

  其中红色的部分是自定义的ViewGroup,这里使用了wrap_content,所以包裹内容。

  我的思路是这样的:

  先自定义一个类,继承自ViewGroup,继承自ViewGroup的自定义类,一定要重写onLayout()方法,因为这是一个抽象方法,主要作用就是用来绘制子View视图,确定ViewGroup中的每个子控件的位置。然后再重写其两个构造函数,一个是一个参数的,一个是两个参数的。继承自ViewGroup与继承自View有些不同的地方,我们在重写onMeasure()的时候,不仅仅要测量自定义的父布局(本自定义View)的尺寸,还要在其中计算其子View的尺寸信息,这是比较麻烦的地方,计算完尺寸还要再onLayout()方法中根据计算出来的控件的摆放位置,调用 子View.layout(int left,int top,int right,int bottom)方法将控件绘制在ViewGroup上,其中的四个参数为子View控件的左上角的点的坐标和右下角的点的坐标信息。这里我们为子View定义了两个自定义属性,实现类似margin的效果,而只要定义了子控件的这类属性,就要自定义一个内部类,继承自MarginLayoutParams类,在这里获取我们在xml中设置的属性信息。也就是主要的方法和与继承自View的自定义View中不同的就是onMeasure()方法,自定义类继承自MarginLayoutParams,onLayout()方法,generateLayoutParams()方法。还不是有点懵?来看看代码,如果是在不懂,可以把代码拷贝到工程中自己改改属性试一下,就会明白不少!

  attr.xml:

 <?xml version="1.0" encoding="utf-8"?>
<resources>
<!--声明自定义属性 自定义ViewGroup的属性 -->
<declare-styleable name="CascadeViewGroup">
<attr name="verticalSpacing" format="dimension|reference"></attr>
<attr name="horizontalSpacing" format="dimension|reference"></attr>
</declare-styleable>
<!--以下自定义属性针对自定义CascadeViewGroup布局中的子控件设置的属性-->
<declare-styleable name="CascadeViewGroup_LayoutParams">
<attr name="layout_paddingLeft" format="dimension|reference"></attr>
<attr name="layout_paddingTop" format="dimension|reference"></attr>
</declare-styleable>
</resources>

  自定义View类:

 package com.example.customviewgroup;

 import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup; /**
*/
public class CascadeViewGroup extends ViewGroup{
//声明自定义布局中的子控件距离父布局的间距的宽度和高度
private int mHoriztonalSpacing;
private int mVerticalSpacing; public CascadeViewGroup(Context context) {
super(context);
} public CascadeViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.CascadeViewGroup);
mHoriztonalSpacing=array.getDimensionPixelSize(
R.styleable.CascadeViewGroup_horizontalSpacing,R.dimen.default_horizontal_spacing);
mVerticalSpacing=array.getDimensionPixelSize(
R.styleable.CascadeViewGroup_verticalSpacing,R.dimen.default_vertical_spacing);
array.recycle();
} /**
* onMeasure 测量自身大小 测量子view的大小
* 并且将子view信息保存到LayoutParams中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取viewgroup中子view 的个数
int count=this.getChildCount();
//获取当前光标的坐标位置 横坐标 纵坐标
int width=getPaddingLeft();
// Log.d("Tag", "base-width: "+width);
int height=this.getPaddingTop();
// Log.d("Tag", "base-height: "+height);
for(int i=0;i<count;i++){
View currentView=getChildAt(i);
//测量每个子view的宽度和高度
measureChild(currentView,widthMeasureSpec,heightMeasureSpec);
LayoutParams lp= (LayoutParams) currentView.getLayoutParams();
//判断子view是否设置自定义属性padding
if(lp.mSettingPaddingLeft!=0){
width+=lp.mSettingPaddingLeft;
}
if(lp.mSettingPaddingTop!=0){
height+=lp.mSettingPaddingTop;
}
//获取子view的起始点坐标
lp.x=width;
lp.y=height;
width+=mHoriztonalSpacing+currentView.getMeasuredWidth();
height+=mVerticalSpacing+currentView.getMeasuredHeight();
}
//因为最后一次循环多加上了一个mHoriztonalSpacing和mVerticalSpacing,
// 所以父布局的wrap_content情况的话需要减掉一个mHoriztonalSpacing和mVerticalSpacing
width+=getPaddingRight()-mHoriztonalSpacing;
height+=getPaddingBottom()-mVerticalSpacing;
//设置自定义ViewGroup的宽度和高度
//resolveSize()主要就是根据指定的尺寸大小和模式 返回需要的大小值
// 自动判断是哪一种模式,并返回需要的尺寸大小(match_parent或者是指定大小或者是wrap_content)
setMeasuredDimension(resolveSize(width,widthMeasureSpec),
resolveSize(height,heightMeasureSpec));
} /**
* 如果想在自定义ViewGroup中使用margin设置间距,则要在自定义类中重写generateLayoutParams系列方法才行
* @return
*/
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
} @Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(this.getContext(),attrs);
} @Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
} /**
* 以内部类的形式自定义LayoutParams 方便测量子view时保存子控件使用的属性值
* 当ViewGroup中的子控件中有自己独特的属性的时候才重写自定义一个类LayoutParams继承自MarginLayoutParams
*/
public static class LayoutParams extends MarginLayoutParams{
//记录子view的起始点
int x;
int y;
//子控件的自定义属性,这里的效果相当于margin的效果
int mSettingPaddingLeft;
int mSettingPaddingTop; public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray array=c.obtainStyledAttributes(attrs,R.styleable.
CascadeViewGroup_LayoutParams);
mSettingPaddingLeft=array.getDimensionPixelSize(R.styleable.
CascadeViewGroup_LayoutParams_layout_paddingLeft,0);
mSettingPaddingTop=array.getDimensionPixelSize(
R.styleable.CascadeViewGroup_LayoutParams_layout_paddingTop,0);
array.recycle();
} public LayoutParams(int width, int height) {
super(width, height);
} public LayoutParams(MarginLayoutParams source) {
super(source);
} public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
} /**
* 确定布局子view的位置
*/
@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 currentView=getChildAt(i);
LayoutParams lp= (LayoutParams) currentView.getLayoutParams();
currentView.layout(lp.x,lp.y,lp.x+currentView.getMeasuredWidth(),
lp.y+currentView.getMeasuredHeight());
}
}
}

  布局文件:

 <?xml version="1.0" encoding="utf-8"?>
<com.example.customviewgroup.CascadeViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
app:horizontalSpacing="10dp"
app:verticalSpacing="10dp"
> <!--<TextView-->
<!--android:layout_width="100dp"-->
<!--android:layout_height="wrap_content"-->
<!--android:background="#ff0000"-->
<!--android:textSize="20sp"-->
<!--android:text="打发打发斯蒂芬" />--> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00ff00"
android:textSize="20sp"
android:text="Hello World!" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00ffff"
android:textSize="20sp"
android:text="Hello World!" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0000ff"
android:textSize="20sp"
android:text="Hello World!" />
</com.example.customviewgroup.CascadeViewGroup>

  基本上就这么多,但是会有一个小Bug,目前还不太清楚是怎么回事,就是在布局文件的根布局下如果不指定我们自定义的app:horizontalSpacing="10dp"和app:verticalSpacing="10dp"这两个属性,在自定义View的第40行和第42行getPaddingLeft()和getPaddingTop()的时候会得到一个非常大的值,即使我们指定了padding的值也是这样,也就是说必须设定这两个自定义属性,我们设置的默认padding没有起作用。不想有间距的话可以设成0dp,这个坑我找到解决办法之后再来填。

自定义View(二)--继承自ViewGroup的更多相关文章

  1. 自定义view(二)

    1.View 的绘制 通过继承View 并重写它的onDraw()来完成绘制. onDraw()有一个参数,就是Canvas对象.使用这个Canvas就可以绘制图像了,Canvas canvas = ...

  2. 自定义控件(视图)2期笔记09:自定义视图之继承自ViewGroup(仿ViewPager效果案例)

    1. 这里我们继承已有ViewGroup实现自定义控件,模拟出来ViewPager的效果,如下: (1)实现的效果图如下: (2)实现步骤: • 自定义view继承viewGroup • 重写onLa ...

  3. 自定义View(一)-ViewGroup实现优酷菜单

    自定义View的第一个学习案例 ViewGroup是自动以View中比较常用也比较简单的一种方式,通过组合现有的UI控件,绘制出一个全新的View 效果如下: 主类实现如下: package com. ...

  4. Android自定义View (二) 进阶

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例 ...

  5. Android 自定义View (二) 进阶

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自定义View之旅,前面已经介绍过一个自定义View的基础的例 ...

  6. 自定义View(四) ViewGroup 动态添加变长Tag标签 支持自动换行

    欲实现如下效果: 思路很简单就2步: 1.测量出ViewGroup的大小 2.找出子View的位置 若要实现动态添加标签view,就要实现ViewGroup的onMeasure().onLayout( ...

  7. 自定义View(二),强大的Canvas

    本文转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android中使用图形处理引擎,2D部分是 ...

  8. Android 自定义View二(深入了解自定义属性attrs.xml)

    1.为什么要自定义属性 要使用属性,首先这个属性应该存在,所以如果我们要使用自己的属性,必须要先把他定义出来才能使用.但我们平时在写布局文件的时候好像没有自己定义属性,但我们照样可以用很多属性,这是为 ...

  9. 自定义View Layout过程 (3)

    目录 目录 1. 知识基础 具体请看我写的另外一篇文章:(1)自定义View基础 - 最易懂的自定义View原理系列 2. 作用 计算View视图的位置. 即计算View的四个顶点位置:Left.To ...

随机推荐

  1. 基于Mongodb的轻量级领域驱动框架(序)

    混园子也有些年头了,从各个大牛那儿学了很多东西.技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告).转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开 ...

  2. C语言标准

    1,K&R C 1978年, Dennis Ritchie和Brian Wilson Kernighan合作出版了<The C Programming Language>的第一版. ...

  3. gcc: error trying to exec 'cc1plus': execvp: 没有那个文件或目录

    问题: 解决办法: 1.没安装G++ 可使用 sudo apt-get install g++ 进行安装 2.gcc版本和g++版本不相符 可利用gcc -v和g++ -v 查看版本

  4. BufferedReader方法-----Scanner方法

    import java.io.*; import java.util.Scanner; public class C { public static void main(String []args) ...

  5. dreamweaver cs5中提示扩展管理不可用

    下载: Extension Manager CS5.5 for Windows 安装后重启就能用了

  6. CentOS服务器的基本配置和查看

    一.设置静态IP 1.修改网卡配置 编辑:vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 #描述网卡对应的设备别名,例如ifcfg-e ...

  7. CentOS7-64bit 编译 Hadoop-2.5.0,并分布式安装

    摘要 CentOS7-64bit 编译 Hadoop-2.5.0,并分布式安装 目录[-] 1.系统环境说明 2.安装前的准备工作 2.1 关闭防火墙 2.2 检查ssh安装情况,如果没有则安装ssh ...

  8. Linux命令-mkdir

    mkdir用于创建空白文件夹 参数-p用于连续创建多层目录 参数-m用于创建自定义的目录权限 [root@localhost test]# mkdir a/b/c/d mkdir: 无法创建目录&qu ...

  9. JSP页面的构成

    JSP页面就是带有JSP元素的常规Web页面,它由静态内容和动态内容构成.其中,静态内容指HTML元素,动态内容(JSP元素)包括指令元素.脚本元素.动作元素.注释等内容. 1.指令元素     指令 ...

  10. Win API 内存整理

    记得我的笔记本上曾经安装了一款名为内存整理大师的软件,当时觉得挺好用而且挺NB的,就是导致开机启动有点慢. 当时我就在想,内存整理是怎么实现的?不过那是水平实在是不怎么样,估计连windows程序的消 ...