Android 自定义View实现多行RadioGroup (MultiLineRadioGroup)
一、项目概况
我们都知道RadioGroup可以实现选择框,但它有一个局限性,由于它是继承自LinearLayout的,所以只能有一个方向,横向或者纵向;但有时候仅一行的RadioGroup并不能满足实际的需求,比如在一行的宽度下显示不完所有的选项,设计上又不允许左右滑动,这时候RadioGroup就不能满足这样的功能设计了;基于此,我写了这个MultiLineRadioGroup并且开源出来;
1、程序界面效果图
2、功能接口
在Api开发上,能够用到的功能及我能想到的,基本都已经添加完毕;具体如下:
- child选项添加,删除
- child选项选中,取消选中
- child对齐方式(左|中|右)
- child行间距,左右间距
- 设置选择模式(单选|多选)
- 获取已选选项
- child选择状态的监听回调查
3、Demo链接地址
https://github.com/a284628487/MultiLineRadioGroup
二、项目分析
1、基于上面的功能设计,为了设计方便,添加了一些自定义属性;
<declare-styleable name="MultiLineRadioGroup">
<attr name="child_margin_horizontal" format="dimension" />
<attr name="child_margin_vertical" format="dimension" />
<attr name="child_layout" format="integer" />
<attr name="child_count" format="integer" />
<attr name="child_values" format="integer" />
<attr name="single_choice" format="boolean" />
<attr name="gravity" format="integer" />
</declare-styleable>
上面的几个自定义属性分别表示
- child水平间距
- child上下间距
- child对应的layout布局文件(后面会讲到,此属性必须配置)
- 初始元素个数
- 初始元素值列表
- 选择模式(单选|多选)
- child对齐方式
2、在layout中使用MultiLineRadioGroup
(1)、定义一个包含MultiLineRadioGroup的xml文件
<org.ccflying.MultiLineRadioGroup
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:child_layout="@layout/child"
app:child_margin_horizontal="6.0dip"
app:child_margin_vertical="2.0dip"
app:child_values="@array/childvalues"
app:single_choice="true" >
</org.ccflying.MultiLineRadioGroup>
(2)、定义一个根节点为CheckBox的layout文件,并把该文件id设置到MultiLineRadioGroup的child_layout属性中(注:该属性必须设置)
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg"
android:button="@null"
android:padding="8.0dip"
android:textColor="@color/text_color" >
</CheckBox>
在MultiLineRadiaGroup中,它的子child元素为CheckBox,所以,必须指定一个要布局为CheckBox的child_layout,这个CheckBox可以根据你的需求设置它的不同状态下的样式;
3、MultiLineRadioGroup 核心方法分析
(1)、onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
childCount = getChildCount();
int flagX = 0, flagY = 0, sheight = 0;
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
measureChild(v, widthMeasureSpec, heightMeasureSpec);
int w = v.getMeasuredWidth() + childMarginHorizontal * 2
+ flagX + getPaddingLeft() + getPaddingRight();
if (w > getMeasuredWidth()) {
flagY++;
flagX = 0;
}
sheight = v.getMeasuredHeight();
flagX += v.getMeasuredWidth() + childMarginHorizontal * 2;
}
rowNumber = flagY;
}
int height = (flagY + 1) * (sheight + childMarginVertical)
+ childMarginVertical + getPaddingBottom() + getPaddingTop();
setMeasuredDimension(getMeasuredWidth(), height);
}
遍历所有的child,并且调用measureChild来对child进行宽高的测量,再通过对宽度的累加与getWidth的值进行比较来判断是否需要换行,并且对需要用到的行数进行记录;
(2)、onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed && !forceLayout) {
Log.d("tag", "onLayout:unChanged");
return;
}
childCount = getChildCount();
int[] sX = new int[rowNumber + 1];
if (childCount > 0) {
if (gravity != LEFT) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
int w = v.getMeasuredWidth() + childMarginHorizontal * 2
+ mX + getPaddingLeft() + getPaddingRight();
if (w > getWidth()) {
if (gravity == CENTER) {
sX[mY] = (getWidth() - mX) / 2;
} else { // right
sX[mY] = (getWidth() - mX);
}
mY++;
mX = 0;
}
mX += v.getMeasuredWidth() + childMarginHorizontal * 2;
if (i == childCount - 1) {
if (gravity == CENTER) {
sX[mY] = (getWidth() - mX) / 2;
} else { // right
sX[mY] = (getWidth() - mX);
}
}
}
mX = mY = 0;
}
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
int w = v.getMeasuredWidth() + childMarginHorizontal * 2 + mX
+ getPaddingLeft() + getPaddingRight();
if (w > getWidth()) {
mY++;
mX = 0;
}
int startX = mX + childMarginHorizontal + getPaddingLeft()
+ sX[mY];
int startY = mY * v.getMeasuredHeight() + (mY + 1)
* childMarginVertical;
v.layout(startX, startY, startX + v.getMeasuredWidth(), startY
+ v.getMeasuredHeight());
mX += v.getMeasuredWidth() + childMarginHorizontal * 2;
}
}
mX = mY = 0;
forceLayout = false;
}
和onMeasure一样,onLayout方法也需要对child进行遍历,不过,在这里的遍历就不是进行测量了,而是对child进行摆放,摆放的时候就需要用到onMeasure方法里面所测量出的子元素的宽高等属性;
遍历可能会遍历两次,如果child对齐方式是非Left的情况下,第一次遍历计算出每行的空隙,然后根据对齐方式算出每行的第一个child的偏移left的距离,第二次遍历的时候,再根据之前算出的偏移距离对child进行layout;
(3)、其它方法
- append(String str) 附加一个child;
- insert(int position, String str) 往指定位置插入child;
- getCheckedValues()|getCheckedItems() 获取选中项;
- remove(int position) 删除指定位置的child;
- setItemChecked(int position) 选中指定位置的child;
- setGravigy(int gravity) 设置child对齐方式;
这些方法都是根据常用或者可能用到的方法来进行实现的,比较简单,就不再贴出代码,上面的Demo链接中都有;
Over!
Android 自定义View实现多行RadioGroup (MultiLineRadioGroup)的更多相关文章
- Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...
- android自定义View之NotePad出鞘记
现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个 ...
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体 ...
- Android自定义View(CustomCalendar-定制日历控件)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeas ...
- Android自定义View(RollWeekView-炫酷的星期日期选择控件)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义Cus ...
- Android自定义View(LineBreakLayout-自动换行的标签容器)
最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦.本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了 ...
- Android自定义View(二、深入解析自定义属性)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 ...
- Android自定义View前传-View的三大流程-Measure
Android自定义View前传-View的三大流程-Measure 参考 <Android开发艺术探索> https://developer.android.google.cn/refe ...
- Android 自定义 View 浅析
Android 自定义 View 浅析 概括 说到自定义 View ,就一定得说说 android 系统的UI绘制流程.再说这个流程之前,我们先看一下在每一个 activity 页面中我们的布局 ui ...
随机推荐
- ASP.NET 5系列教程 (二):Hello World
本篇文章内容比较基础,主要是向大家展示如何创建一个 ASP.NET 5 工程,主要包含内容如下: 创建ASP.NET 5 工程 添加 Todo 控制器 安装 K Version Manager 执行 ...
- [BTS] Exception occurred when persisting state to the database
Error 1 Uncaught exception (see the 'inner exception' below) has suspended an instance of service 'J ...
- 常用js方法整理common.js
项目中常用js方法整理成了common.js var h = {}; h.get = function (url, data, ok, error) { $.ajax({ url: url, data ...
- [转]HTTP协议及其请求头分析
众所周知,Internet的基本协议是TCP/IP协议,目前广泛采用的FTP.Archie Gopher等是建立在TCP/IP协议之上的应用层协议,不同的协议对应着不同的应用. WWW服务器使用 ...
- 15个专业版响应式WordPress主题
当你准备创建一个新的网站,你会发现有很多的WordPress主题供你选择,它们强大而且合适.虽然大量的第三方插件可以解决脚本的问题,但这次我们要向你介绍一些完全不同于以往的主题,它们都是专业开发者开发 ...
- GUID全局唯一标识符相关知识了解
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符.GUID主要用于在拥有多个节点.多台计算机的网络或系统中.在理想情 ...
- beego中orm关联查询使用解析
这两天在学习beego框架,之前学习的时候遗漏了很多东西,比如orm.缓存.应用监控.模板处理等,这里将通过实例记录下如何使用beego自带的orm进行关联查询操作. 首先说明下,beego的orm有 ...
- 巧用Windows 7计划任务设置定时提醒
Windows 7系统有个“计划任务”功能,一般人都很少使用.其实,“计划任务”是系统自带的一个很实用的功能,比如说,这个功能可以设置定时提醒,这样在使用电脑时就不会因为太过投入而导致错过重要的事务. ...
- 只写104行代码!在nopCommerce中如何实现自动生成网站地图
表告诉我说你不知道nopCommerce是什么.它是目前.NET中最流行的完全开源网上商城,由俄罗斯的团队在2008年开始立项一直开发到现在已经是3.3版本了.代码目前托管在codeplex上,有兴趣 ...
- Java SimpleDateFormat[转]
[补充] [转] http://stackoverflow.com/questions/2603638/why-cant-this-simpledateformat-parse-this-date-s ...