简单的例子了解自定义ViewGroup(一)
在Android中,控件可以分为ViewGroup控件与View控件。自定义View控件,我之前的文章已经说过。这次我们主要说一下自定义ViewGroup控件。ViewGroup是作为父控件可以包含多个View控件,并管理其中包含的View控件。
一般自定义ViewGroup的流程如下:
onMeasure()
onLayout()
我们一般不需要像自定义View一样重写onDraw(),这里需要多写一个onLayout来摆放子View的位置。除了onLayout方法之外,我们还需要确定LayoutParams,这个是子View告诉父布局的一些参数信息,比如MarginLayoutParams,则表明该ViewGroup支持margin,当然这个也可以没有。
下面我们通过一个例子来说明简单的自定义ViewGroup
一个简单的例子
这个例子,我们将写一个ViewGroup,该ViewGroup中4个button排成一列。这个例子主要说明onMeasure和onLayout的写法。
首先我们新建一个MyViewGroup继承ViewGroup,然后重写onMeasure方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
这个重写非常的简单,调用父类的测量方法,然后测量所有的子控件的,只要子控件不是wrap_content都会测量精准。这里为了简单,没有去考虑wrap_content的情况,后面我们完善的时候会说道。
然后重写onLayout()方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int height = 0;
int count = getChildCount();
View child;
Log.e("ri", count + "");
for(int i = 0 ;i < count;i++) {
child = getChildAt(i);
child.layout(0, height, child.getMeasuredWidth(),height + child.getMeasuredHeight());
height += child.getMeasuredHeight();
}
}
这里的代码很好理解,因为我们要实现4个button一列显示,然后每个子View的宽度是一样的,并且每个子View的left和right是一样的。所以每个子View只有top和bottom不一样。我们首先定义个高度height初始为0,然后得到所有子View的个数,依次设置每个子View的top和bottom。top就是定义的height,bottom则为height加上子View的高度。设置完后height累加。
xml中布局如下:
<com.example.byhieg.viewpratice.MyViewGroup android:layout_height="match_parent"
android:layout_width="match_parent">
<Button
android:text="1"
android:layout_width="50dp"
android:layout_height="80dp" />
<Button
android:text="2"
android:layout_width="50dp"
android:layout_height="80dp" />
<Button
android:text="3"
android:layout_width="50dp"
android:layout_height="80dp" />
<Button
android:text="4"
android:layout_width="50dp"
android:layout_height="80dp" />
</com.example.byhieg.viewpratice.MyViewGroup>
效果如下:
下面,我们继续完善这个控件,让他适应wrap_content。这里,我们主要重写onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
//开始处理wrap_content,如果一个子元素都没有,就设置为0
if (getChildCount() == 0) {
setMeasuredDimension(0,0);
} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//ViewGroup,宽,高都是wrap_content,根据我们的需求,宽度是子控件的宽度,高度则是所有子控件的总和
View childOne = getChildAt(0);
int childWidth = childOne.getMeasuredWidth();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(childWidth, childHeight * getChildCount());
} else if (widthMode == MeasureSpec.AT_MOST) {
//ViewGroup的宽度为wrap_content,则高度不需要管,宽度还是自控件的宽度
View childOne = getChildAt(0);
int childWidth = childOne.getMeasuredWidth();
setMeasuredDimension(childWidth,heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
//ViewGroup的高度为wrap_content,则宽度不需要管,高度为子View的高度和
View childOne = getChildAt(0);
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(widthSize, childHeight * getChildCount());
}
}
主要通过注释,就可以很明白wrap_content的情况下,如何计算viewGroup的高度和宽度。在viewGroup的onMeasure,我们是不需要在这里面考虑每一个View的宽高,这个通过measureChildren来通知每一个子View自己测量的。我们只需要考虑viewGroup的宽高在自适应的情况下,该是多大。
LayoutParams
在上面这个简单的ViewGroup中,我们是没有设置margin的,也就是说,即使我们在子View中设置了margin也是没有效的。我们需要修改我们的自定义ViewGroup来适应margin的情况。这里我们为了简化情况,只设定第一个button有一个android:layout_margin="30dp"
的属性。
这里,我们修改onMeasure方法,让viewGroup的宽度变为原来的宽度加上margin的宽度,高度也是原来的高度加上margin的高度。代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
MarginLayoutParams params = null;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
//开始处理wrap_content,如果一个子元素都没有,就设置为0
if (getChildCount() == 0) {
setMeasuredDimension(0,0);
} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//ViewGroup,宽,高都是wrap_content,根据我们的需求,宽度是子控件的宽度,高度则是所有子控件的总和
View childOne = getChildAt(0);
params = (MarginLayoutParams) childOne.getLayoutParams();
int childWidth = childOne.getMeasuredWidth();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,
childHeight * getChildCount() + params.topMargin + params.bottomMargin);
} else if (widthMode == MeasureSpec.AT_MOST) {
//ViewGroup的宽度为wrap_content,则高度不需要管,宽度还是自控件的宽度
View childOne = getChildAt(0);
params = (MarginLayoutParams) childOne.getLayoutParams();
int childWidth = childOne.getMeasuredWidth();
setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
//ViewGroup的高度为wrap_content,则宽度不需要管,高度为子View的高度和
View childOne = getChildAt(0);
params = (MarginLayoutParams) childOne.getLayoutParams();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(widthSize, childHeight * getChildCount() + params.topMargin + params.bottomMargin);
}
}
这里,注意这个语句params = (MarginLayoutParams) childOne.getLayoutParams();
如果不重写layoutParams相关的代码,这样直接转换会出现问题。所以,我们需要重写如下代码:让他返回MarginLayoutParams类型的对象
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
同样,我们已经测量得到了viewGroup的宽和高,接下来,需要对添加了margin的view,重新摆放。主要的摆放规则,左边的坐标为Leftmargin,第一个view的上面的坐标为topMargin,同时,第二个view的上面的坐标要加上bottomMargin。这个只是一个简单的例子来说明放入margin之后要怎么考虑,一般不会这么具体到只计算第一个view的Margin。代码看看就好
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int height = 0;
int count = getChildCount();
View child;
Log.e("ri", count + "");
child = getChildAt(0);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
int c1 = params.leftMargin;
int c2 = params.topMargin;
int c3 = c1 + child.getMeasuredWidth();
int c4 = c2 + child.getMeasuredHeight();
child.layout(c1,c2, c3,c4);
height = c4 + params.bottomMargin;
for(int i = 1 ;i < count;i++) {
child = getChildAt(i);
child.layout(c1, height, c3, height + child.getMeasuredHeight());
height += child.getMeasuredHeight();
}
}
总结
这是自定义ViewGroup的第一篇文章,自定义ViewGroup是比自定义View知识点更多,而且应用也更广泛。这篇只是简单的介绍了自定义ViewGroup中需要复写的方法,但是一般的viewGroup不会是这样的。上面的东西,用一个LinearLayout布局实现,然后include该布局更方便。一般viewGroup都需要我们来实现view事件的分发以及滑动的处理,接下来的文章,讲介绍滑动的多种方式。
简单的例子了解自定义ViewGroup(一)的更多相关文章
- [安卓] 18、一个简单的例子做自定义动画按钮和自定义Actionbar
在做安卓UI的时候有时候需自定义具有动画效果的按钮或需要自定义一下actionbar~ 本节用一个简单的demo讲如何自定义具有动画效果的按钮,以及个性化的actionbar 下面是效果: 其中: △ ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动, ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...
- Jsp2.0自定义标签(第一天)——一个简单的例子
今天是学习自定义标签的第一天 Jsp2.0以来,自定义标签的实现比传统标签的实现容易了很多,一般只要extends类SimpleSupport重写doTag()方法即可. 先看最简单的例子,输出一个H ...
- 简单的自定义ViewGroup
自定义ViewGroup需要重写onMeasure, onLayout等方法.下面是一个实例,4个View分别显示在四个角. public class MyGroup extends ViewGrou ...
- android 手把手教您自定义ViewGroup(一)
1.概述 在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥? ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属 ...
- Android之自定义ViewGroup
概述 在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥? ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性, ...
- Android ViewDragHelper完全解析 自定义ViewGroup神器
Android ViewDragHelper完全解析 自定义ViewGroup神器 转载请标明出处: http://blog.csdn.net/lmj623565791/article/detai ...
- Kotlin 第一弹:自定义 ViewGroup 实现流式标签控件
古人学问无遗力, 少壮工夫老始成.纸上得来终觉浅, 绝知此事要躬行. – 陆游 <冬夜读书示子聿> 上周 Google I/O 大会的召开,宣布了 Kotlin 语言正式成为了官方开发语言 ...
随机推荐
- Linux 江湖系列阶段性总结
引言 我使用 Linux 已经有很多年了,最开始接触 Linux 的时候是从 RedHat 9(没有 Enterprise),中途换过 N 个不同的发行版.多年前,我在 BlogJava 上面分享 J ...
- AngularJS过滤器filter-保留小数,小数点-$filter
AngularJS 保留小数 默认是保留3位 固定的套路是 {{deom | number:4}} 意思就是保留小数点 的后四位 在渲染页面的时候 加入这儿个代码 用来精确浮点数,指定小数点 ...
- 7.让网站支持http和https的访问方式
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html#iis 怎么让网站在本地支持SSL?http://www.c ...
- WPF 微信 MVVM
公司的同事离职了,接下来的日子可能会忙碌,能完善DEMO的时间也会少了,因此,把做的简易DEMO整体先记录一下,等后续不断的完善. 参考两位大神的日志:WEB版微信协议部分功能分析.[完全开源]微信客 ...
- Java 堆内存与栈内存异同(Java Heap Memory vs Stack Memory Difference)
--reference Java Heap Memory vs Stack Memory Difference 在数据结构中,堆和栈可以说是两种最基础的数据结构,而Java中的栈内存空间和堆内存空间有 ...
- .NET中AOP方便之神SheepAspect
SheepAspect 简介以及代码示列: SheepAspect是一个AOP框架为.NET平台,深受AspectJ.它静织目标组件作为一个编译后的任务(编译时把AOP代码植入). 多有特性时,可根据 ...
- Android Weekly Notes Issue #236
Android Weekly Issue #236 December 18th, 2016 Android Weekly Issue #236 本期内容包括: Google的物联网平台Android ...
- caffe的python接口学习(7):绘制loss和accuracy曲线
使用python接口来运行caffe程序,主要的原因是python非常容易可视化.所以不推荐大家在命令行下面运行python程序.如果非要在命令行下面运行,还不如直接用 c++算了. 推荐使用jupy ...
- Xamarin.Android-捕获未处理异常(全局异常)
一.前言 android中如果出现了未处理的异常,程序会闪退,这是非常不好的用户体验,很多用户会因此卸载APP,因此未处理的异常是应该尽力避免的. 有些很难避免的异常(如:IO.网络等),应在代码中进 ...
- 博客已经迁移至 http://barretlee.com/entry/,时而同步分享到这里
博客园是一个十分好的写作平台,不过个人比较喜欢倒腾,所以将文章都做了搬迁. 博客已经迁移至 http://barretlee.com/entry/,感谢一直以来的关注和支持. 博客订阅地址: http ...