Android-浅析自定义ViewGroup(附一个子控件根据父控件行宽自动换行的LineWrapLayout 案例)
来源:互联网 发布:ubuntu cab软件 编辑:程序博客网 时间:2024/06/05 18:43
转载请注明出处:http://blog.csdn.net/teisun/article/details/45560095
前言
做了快4年的android开发没写过什么技术文章,最近工作空档较多所以想起来写一两篇博文分享出来并且提升自己,文采拙劣,欢迎拍砖。
View的绘制过程
ViewGroup继承View,ViewGourp可以包含很多个View,View的绘制过程分三个步骤:
- onMeasure 计算,可以把View想象成一张无限大的画布,那么你要画出来的内容的尺寸有多大? onMeasure就是用于计算内容尺寸边界的。
- onLayout 布局,内容是由元素组成的,决定好画布的尺寸后各个子View要放在画布的什么位置呢?onLayout就是让你决定各个子View要放在画布的什么位置。
- onDraw 画,决定好子View的位置后,那我们的子View长什么样子呢?是画鸡、画猫还是画狗呢,只有各个子View自己知道,dispatchDraw 通知各个子View在指定的位置draw出自己。
onMeasure
onMeasure函数的widthMeasureSpec与heightMeasureSpec并非只是个简单的数字而是一种特殊的值,是期望类型值与尺寸值相加得到的,而android中的View.MeasureSpec类提供了对这种特殊值得操作方法:getSize,getMode ,makeMeasureSpec。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //getSize 获取控件宽度 int with = MeasureSpec.getSize(widthMeasureSpec); //getMode 获取父View对其宽度的期望值类型 int mode = MeasureSpec.getMode(widthMeasureSpec); //makeMeasureSpec 根据给定的尺寸和期望类型生成一个MeasureSpec值,一般用于指定子View的尺寸 View child = getChildAt(0); LayoutParams lp=(LayoutParams)child.getLayoutParams(); //MeasureSpec.AT_MOST:表示子View最多只能是lp.width指定的大小,开发人员和系统都应该按照这个规则去设置子View的大小,当然开发人员也可以任性地自己去设置想要的大小。 int wSpec=MeasureSpec.makeMeasureSpec(lp.width,MeasureSpec.AT_MOST); int hSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.AT_MOST); //MeasureSpec.EXACTLY:表示子View只能是lp.width指定的大小,开发人员和系统都应该按照这个尺寸去设置子View的大小,当然开发人员也可以任性地自己去设置想要的大小。 int wSpec=MeasureSpec.makeMeasureSpec(lp.width,MeasureSpec.EXACTLY); int hSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); //MeasureSpec.UNSPECIFIED:表示子View的尺寸大小没有任何限制. int wSpec=MeasureSpec.makeMeasureSpec(lp.width,MeasureSpec.UNSPECIFIED); int hSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.UNSPECIFIED); child.measure(wSpec, hSpec); }
更多关于MeasureSpec与android中View的Flag设计会在其他文章中说明这里不再展开。
onLayout
官方解释:
Called from layout when this view should assign a size and position to each of its children.
也就是说当需要为每一个子View指定尺寸和位置时会被调用。
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int mTotalHeight = 0; // 遍历子View int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 获取在onMeasure中计算的视图尺寸 int measureHeight = childView.getMeasuredHeight(); int measuredWidth = childView.getMeasuredWidth(); //告诉ViewGroup子View要的大小和位置 childView.layout(left, mTotalHeight, measuredWidth, mTotalHeight + measureHeight); mTotalHeight += measureHeight; } }
onDraw
onDraw 方法一般自定义View的时候会用到,下一篇文章再详细分析。
案例:LineWrapLayout 实现自动换行的自定义ViewGroup
android提供的Linearlayout不可以自动换行显示,那么我们自己写一个,先看效果:
实现思路
- 什么时候应该换行?
答:一行内所有子View的宽度和View之间的间隔相加>=ViewGroup的宽度时就应该换行,而且最后一个View就应该被排列到下一行。 - 如何计算ViewGroup的高度?
答:ViewGroup的上下Padding+每一行高度+行之间纵向间隔 - 可以设置子View与子View之间的横向纵向间隔
- onMeasure函数中实现ViewGroup的宽高计算逻辑与每一个子View的宽高。
- onLayout函数中实现各个子View的排列逻辑
好了,想太多没用有个大概的实现思路就可以动手写了。
主要代码
我想到的是使用xml文件配置子View之间的间隔这就需要用到自定义属性,在values文件夹下创建attrs.xml,代码:
//name写自定义控件的类名,每一个attr指定属性名和属性类型 <declare-styleable name="LineWrapLayout"> //横向间隔 <attr name="horizontal_spacing" format="dimension" /> //纵向间隔 <attr name="vertical_spacing" format="dimension" /> </declare-styleable>
使用:
<com.vclubs.ui.component.LineWrapLayout //使用自定义属性必须加这一句 xmlns:app="http://schemas.android.com/apk/res-auto" //使用自定义属性指定子View之间的横向纵向间隔 app:horizontal_spacing="@dimen/dp15" app:vertical_spacing="@dimen/dp15" android:id="@+id/pic_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/dp40" android:layout_marginRight="@dimen/dp40" android:orientation="horizontal" android:paddingLeft="@dimen/dp20" android:paddingRight="@dimen/dp20" android:paddingTop="@dimen/dp20" ></com.vclubs.ui.component.LineWrapLayout>
代码中获取自定义属性的值:
public LineWrapLayout(Context context, AttributeSet attrs) { super(context, attrs); //在构造函数中获得自定义属性的值,ViewGroup有三个构造函数最好都实现一下 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LineWrapLayout); //得到横向间隔 hSpacing = a.getDimensionPixelSize(R.styleable.LineWrapLayout_horizontal_spacing, 15); //得到纵向间隔 vSpacing = a.getDimensionPixelSize(R.styleable.LineWrapLayout_vertical_spacing, 15); a.recycle(); }
在onMeasure函数中计算ViewGroup尺寸与各个子View的尺寸:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //得到ViewGroup的初始宽高 final int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec) + getPaddingBottom()+getPaddingTop(); final int count = getChildCount(); int line_height = 0; //获取第一个子View的起始点位置 int xpos = getPaddingLeft(); int ypos = getPaddingTop(); //计算每一个子View的尺寸,并算出ViewGroup的高度 for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = child.getLayoutParams(); //算出子View宽的MeasureSpec值 int wSpec = MeasureSpec.makeMeasureSpec( lp.width, MeasureSpec.AT_MOST); //算出子View高的MeasureSpec值 int hSpec = MeasureSpec.makeMeasureSpec( lp.height, MeasureSpec.AT_MOST); //让子View记住自己宽高的MeasureSpec值,子View的 //onMeasure(int widthMeasureSpec,int heightMeasureSpec) //函数传入的就是这里算出来的这两个值 child.measure(wSpec, hSpec); //设置完MeasureSpec值后调用View.getMeasuredWidth()函数算出View的宽度 final int childw = child.getMeasuredWidth(); //记录最大行高(子View的高度有可能不一样,行高取最大高度) line_height = Math.max(line_height, child.getMeasuredHeight() + vSpacing); if (xpos + childw > width) { //初始坐标的x偏移值+子View宽度>ViewGroup宽度 就换行 xpos = getPaddingLeft();//坐标x偏移值归零 ypos += line_height;//坐标y偏移值再加上本行的行高也就是换行 } //算出下一个子View的起始点x偏移值 xpos += childw + hSpacing; } this.line_height = line_height; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { //对高度期望值没有限制 height = ypos + line_height; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { //达不到指定高度则缩小高度 if (ypos + line_height < height) { height = ypos + line_height; } } else { height = ypos + line_height; } //设置ViewGroup宽高值 setMeasuredDimension(width, height); }
在onLayout函数中设置各个子View的位置,有了上面的基础下面的代码应该很好理解了,由读者自行理解吧就不注释了。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); final int width = r - l; int xpos = getPaddingLeft(); int ypos = getPaddingTop(); //设置每一个子View的位置,左上角xy坐标与右下角xy坐标确定View的位置 for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childw = child.getMeasuredWidth(); final int childh = child.getMeasuredHeight(); if (xpos + childw > width) { xpos = getPaddingLeft(); ypos += line_height; } child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + hSpacing; } } }
结束
大概内容已经写完了以后再完善,源码:
https://github.com/teisun/Android-LineWrapLayout
http://download.csdn.net/detail/teisun/8755303
- Android-浅析自定义ViewGroup(附一个子控件根据父控件行宽自动换行的LineWrapLayout 案例)
- 子控件根据父控件行宽自动换行---LineWrapLayout实现
- 自定义根据屏幕宽度子控件根据父控件行宽自动换行的ViewGroup
- 使子控件自动换行的viewgroup
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android自定义ViewGroup之子控件的自动换行和添加删除
- Android 自定义自动换行控件
- 子控件自动换行的父容器
- Android自定义ViewGroup添加组合控件的子view
- 安卓自定义控件之自动换行ViewGroup
- [自定义控件系列2]--进阶篇:可自动换行的ViewGroup
- Android 自定义ViewGroup控件
- android自定义控件自动换行效果实现
- Maven项目无法产生Maven Dependencies
- Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView
- vs(win7) 报错
- 如何在Ubuntu QML应用中播放视频
- 关于Apache端口被类似VMware软件占用的解决方案
- Android-浅析自定义ViewGroup(附一个子控件根据父控件行宽自动换行的LineWrapLayout 案例)
- 复数类中的运算符重载
- 【USACO4.1.2】栅栏的木料 爆搜剪枝优化
- 本地化app名字
- Ajax跨域技术
- [nginx源码分析]配置合并
- Android:LayoutInflater的使用
- 4.Magento的模型和ORM基础
- 剑指offer 面试题25—二叉树中和为某一值的路径