视图分类就两类,View和ViewGroup。ViewGroup是View的子类,ViewGroup可以包含所有的View(包括ViewGroup),View只能自我描绘,不能包含其他View。

然而系统定义的ViewGroup毕竟功能有限,不能满足我们所有的需求,很简单的道理,别人不可能为你考虑所有的细节。所以我们需要自定义ViewGroup。


一个非常简单的视图包含关系:ViewGroup1->ViewGroup2->…->ViewGroupi->…->ViewGroupn->View(->为包含)

其中包含了两个极为重要的流程:尺寸测量和位置摆放。尺寸测量完后再走位置摆放。

1、尺寸测量

View有一个尺寸测量方法onMeasure(int widthMeasureSpec, int heightMeasureSpec),这个方法负责设置自己的大小,以及发送建议的大小到他的children(如果他有的话)。

2、位置摆放

只有ViewGroup才有位置摆放一说。View有一个摆放方法onLayout(boolean changed, int left, int top, int right, int bottom),此方法负责摆放他的children的位置。


起码得有一个开头的ViewGroup,让其下发建议的大小下去到他的children里面。查看Activity源代码可以看到以下一个方法setContentView(View v):

@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}

这个方法不要太熟悉,就是我们平时在onCreate中用的设置布局的方法。可以看到用了一个ID为android.R.id.content的视图contentParent来添加我们传入的v。contentParent是一个大小固定的ViewGroup,因为屏幕大小和状态栏的大小是固定的。我们可以把这个contentParent当作一个开头的ViewGroup。


然后就是如何传递建议的大小。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)中的measureSpec(widthMeasureSpec或heightMeasureSpec)包含了两个信息:测量模式specMode和测量大小specSize。获取方法:

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

specMode有三个模式:

1、View.EXACTLY:建议恰好以specSize大小设置

2、View.AT_MOST:建议最多以specSize大小设置

3、View.UNSPECIFIED:没有建议,任意发挥

比如一个RelativeLayout,其中包括一个View:

1、这个View设置的layout_width为100dp,那么RelativeLayout就会把View.EXACTLY和100dp组合成一个widthMeasureSpec发送到View的onMeasure方法里。

2、这个View设置的layout_width为wrap_content,那么RelativeLayout就会把View.AT_MOST和自身可用宽度组合成一个widthMeasureSpec发送到View的onMeasure方法里。

3、View.UNSPECIFIED多用于大小不可控的地方,比如ScrollView中的子视图View,ScrollView会把View.UNSPECIFIED和大小值为0组合成一个heightMeasureSpec发送到View的onMeasure方法里。

测量模式和测量大小只是建议性,至于children会不会采纳就是他们的事情了。


接下来就是如何摆放位置。

onLayout(boolean changed, int left, int top, int right, int bottom)中的left、top、right、bottom是指相对于父视图的偏移值,我们可以利用这些值来指出它的大小,width=right-left;height=bottom-top。

然后就循环children把他们摆放好。


下面进行实战

1、简单的自定义:

外面一个ViewGroup,里面动态添加View,View的排序为从左到右。

布局为:

<com.besttimer.study.myviewgrouptest.CustomViewGroup
android:layout_width="300dp"
android:layout_height="100dp"> <com.besttimer.study.myviewgrouptest.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffff0000" /> <com.besttimer.study.myviewgrouptest.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff00ff00" /> <com.besttimer.study.myviewgrouptest.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000ff" />
</com.besttimer.study.myviewgrouptest.CustomViewGroup>

CustomViewGroup的测量方法为:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int measuredWidth = this.getMeasureSize(widthMeasureSpec, 100);
int measuredHeight = this.getMeasureSize(heightMeasureSpec, 100); int childCount = this.getChildCount();
int childWidth = measuredWidth / childCount;
int childHeight = measuredHeight; for (int index = 0; index < childCount; index++) {
View childView = this.getChildAt(index);
//以MeasureSpec.EXACTLY定义测量值
int widthMeasureSpec_child = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int heightMeasureSpec_child = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
//此方法将会调用childView.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
childView.measure(widthMeasureSpec_child, heightMeasureSpec_child);
} //必须要设置的大小,指定其在父视图中的大小
this.setMeasuredDimension(measuredWidth, measuredHeight); } /**
* 获取测量大小
*
* @param measureSpec
* @param defaultValue 默认大小
* @return
*/
private int getMeasureSize(int measureSpec, int defaultValue) { int result = defaultValue; int specMode = MeasureSpec.getMode(measureSpec);//测量模式
int specSize = MeasureSpec.getSize(measureSpec);//测量大小
switch (specMode) {
//如果是无建议的测量模式,则取默认值
case MeasureSpec.UNSPECIFIED:
result = defaultValue;
break;
//建议最多以specSize大小设置
case MeasureSpec.AT_MOST:
//建议恰好以specSize大小设置
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

CustomViewGroup的摆放方法为:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = this.getChildCount();
int childL = 0;
int childT = 0;
for (int index = 0; index < childCount; index++) {
View childView = this.getChildAt(index);
//经过onMeasure计算后,在此方法中已经可以获取大小了
int childMeasureWidth = childView.getMeasuredWidth();
int childMeasureHeight = childView.getMeasuredHeight();
//并排摆放
childView.layout(childL, childT, childL + childMeasureWidth, childT + childMeasureHeight);
childL += childMeasureWidth;
} }

CustomView的测量方法为:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //View中只需要设置自己的大小,毕竟没有children
int measuredWidth = this.getMeasureSize(widthMeasureSpec, 0);
int measuredHeight = this.getMeasureSize(heightMeasureSpec, 0);
this.setMeasuredDimension(measuredWidth, measuredHeight); } /**
* 获取测量大小
*
* @param measureSpec
* @param defaultValue 默认大小
* @return
*/
private int getMeasureSize(int measureSpec, int defaultValue) { int result = defaultValue; int specMode = MeasureSpec.getMode(measureSpec);//测量模式
int specSize = MeasureSpec.getSize(measureSpec);//测量大小
switch (specMode) {
//如果是无建议的测量模式,则取默认值
case MeasureSpec.UNSPECIFIED:
result = defaultValue;
break;
//建议最多以specSize大小设置
case MeasureSpec.AT_MOST:
//建议恰好以specSize大小设置
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

2、添加declare-styleable

像LinearLayout、RelativeLayout这些都有padding这些属性,我们也可以添加。效果图:

新建一个xxx.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomViewGroup_attrs">
<attr name="padding" format="dimension" />
</declare-styleable>
</resources>

其中format有几类,现用到尺寸dimension类型。

在CustomViewGroup中增加一个初始化方法:

private int padding = 0;

private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomViewGroup_attrs);
//获取padding值
this.padding = (int) a.getDimension(R.styleable.CustomViewGroup_attrs_padding, 0);
//记得回收
a.recycle();
}

修改CustomViewGroup的一些方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... int childCount = this.getChildCount();
int childWidth = (measuredWidth - this.padding * 2) / childCount;
int childHeight = measuredHeight - this.padding * 2; ... }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = this.getChildCount();
int childL = this.padding;
int childT = this.padding;
... }

3、添加LayoutParams

TextView、ImageView这些在RelativeLayout中都有layout_centerVertical垂直居中选项,这是RelativeLayout的LayoutParams里面的属性,实际上layout_width、layout_height也是LayoutParams里面的属性。

垂直居中效果图:

修改布局文件

<com.besttimer.study.myviewgrouptest.CustomViewGroup
android:layout_width="300dp"
android:layout_height="100dp"
android:background="#ff000000"
app:padding="10dp"> <com.besttimer.study.myviewgrouptest.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffff0000" /> <com.besttimer.study.myviewgrouptest.CustomView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:background="#ff00ff00"
app:layout_centerVertical="true" /> <com.besttimer.study.myviewgrouptest.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000ff" />
</com.besttimer.study.myviewgrouptest.CustomViewGroup>

修改xxx.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomViewGroup_attrs">
<attr name="padding" format="dimension" />
<attr name="layout_centerVertical" format="boolean" />
</declare-styleable>
</resources>

为CustomViewGroup添加自定义LayoutParams

public static class LayoutParams extends ViewGroup.LayoutParams {

    public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
this.init(c, attrs);
} private boolean layout_centerVertical = false;//是否垂直居中 private void init(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomViewGroup_attrs);
//获取layout_centerInParent值
this.layout_centerVertical = a.getBoolean(R.styleable.CustomViewGroup_attrs_layout_centerVertical, false);
//记得回收
a.recycle();
} } @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(this.getContext(), attrs);
}

修改测量和摆放方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... for (int index = 0; index < childCount; index++) {
View childView = this.getChildAt(index);
//以MeasureSpec.EXACTLY定义测量值
int childNewHeight = childHeight;
CustomViewGroup.LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
//如果大于0说明传了一个具体的数值
if (layoutParams.height > 0) {
childNewHeight = layoutParams.height;
}
int widthMeasureSpec_child = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int heightMeasureSpec_child = MeasureSpec.makeMeasureSpec(childNewHeight, MeasureSpec.EXACTLY);
//此方法将会调用childView.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
childView.measure(widthMeasureSpec_child, heightMeasureSpec_child);
} ... }
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { ...
for (int index = 0; index < childCount; index++) {
View childView = this.getChildAt(index);
//经过onMeasure计算后,在此方法中已经可以获取大小了
int childMeasureWidth = childView.getMeasuredWidth();
int childMeasureHeight = childView.getMeasuredHeight();
//并排摆放
CustomViewGroup.LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
int childNewT = childT;
if (layoutParams.layout_centerVertical) {
childNewT += (b - t - this.padding * 2 - childMeasureHeight) / 2;
}
childView.layout(childL, childNewT, childL + childMeasureWidth, childNewT + childMeasureHeight);
childL += childMeasureWidth;
} }

其中layout_width和layout_height是ViewGroup.LayoutParams的属性,已经实现好了获取逻辑,直接用就是了。

源代码地址:http://files.cnblogs.com/files/linyibiao/AndroidProject.zip

Android自定义ViewGroup的更多相关文章

  1. android自定义viewgroup之我也玩瀑布流

    先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...

  2. Android自定义ViewGroup,实现自动换行

    学习<Android开发艺术探索>中自定义ViewGroup章节 自定义ViewGroup总结的知识点 一.自定义ViewGroup中,onMeasure理解 onMeasure(int ...

  3. android自定义viewgroup实现等分格子布局

    先上效果图: 实现这样的效果: 一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果 还有就是利用g ...

  4. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu

    示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动, ...

  5. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]

    http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...

  6. Android自定义ViewGroup(四、打造自己的布局容器)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51500304 本文出自:[openXu的博客] 目录: 简单实现水平排列效果 自定义Layo ...

  7. android自定义viewgroup初步之一----抽屉菜单

    转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...

  8. Android 自定义ViewGroup手把手教你实现ArcMenu

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...

  9. Android -- 自定义ViewGroup实现FlowLayout效果

    1,在开发的时候,常在我们的需求中会有这种效果,添加一个商品的一些热门标签,效果图如下: 2,从上面效果可以看得出来,这是一个自定义的ViewGroup,然后实现换行效果,让我们一起来实现一下 自定义 ...

随机推荐

  1. lucene 基础知识点

    部分知识点的梳理,参考<lucene实战>及网络资料 1.基本概念 lucence 可以认为分为两大组件: 1)索引组件 a.内容获取:即将原始的内容材料,可以是数据库.网站(爬虫).文本 ...

  2. 新应用上线 Snippet

    Snippet 是一款代码片段收集工具,经过一天三夜的开发终于上线了. 应用地址:snippets.barretlee.com 源码地址:barretlee/snippets 由于使用原生 JS 开发 ...

  3. Angular移除不必要的$watch之性能优化

    双向绑定是Angular的核心概念之一,它给我们带来了思维方式的转变:不再是DOM驱动,而是以Model为核心,在View中写上声明式标签.然后,Angular就会在后台默默的同步View的变化到Mo ...

  4. Egret3D 研究报告(一)初试

    了解的朋友应该知道我最近一直都在鼓吹webgl. 今天有一点时间,加了一个Egret3D的群,就开始了这个坑. 耳听为虚,眼见为实.让我们荡起双桨,一起去刷一下egret 打开姿势 至于以什么姿势打开 ...

  5. C#中,双屏/两屏/三屏/多屏跳转判断

    之前伤脑筋写过一次在Web中,JS,ActiveXObject去读取显示器数量.分辨率去判断单双屏跳转. 那么在客户端中,用C#去读取硬件信息,更方便更容易! 思路参考代码: ) { //此显示器是否 ...

  6. Python开发环境配置

    好久没有写博客了,自从6月份毕业后,进入一家做书法.字画文化宣传的互联网公司(www.manyiaby.com),这段时间一直在进行前端开发,对于后端的使用很少了,整天都是什么html.css.jav ...

  7. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(12)-系统日志和异常的处理②

    系列目录 上一讲我们做了日志与异常的结果显示列表,这一节我们讲要把他应用系统中来. 首先我们在App.Common类库中创建一个通用类ResultHelper,这个类里面写了,获取一个GUID,获取当 ...

  8. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  9. 使用Google Closure Compiler高级压缩Javascript代码注意的几个地方

    介绍 GCC(Google Closure Compiler)是由谷歌发布的Js代码压缩编译工具.它可以做到分析Js的代码,移除不需要的代码(dead code),并且去重写它,最后再进行压缩. 三种 ...

  10. ASP.NET 访问共享文件夹

    配置代码: var dataProtection = new Microsoft.AspNet.DataProtection.DataProtectionProvider(new DirectoryI ...