1、项目概况android
咱们都知道RadioGroup能够实现选择框,但它有一个局限性,因为它是继承自LinearLayout的,因此只能有一个方向,横向或者纵向;但有时候仅一行的RadioGroup并不能知足实际的需求,好比在一行的宽度下显示不完全部的选项,设计上又不容许左右滑动,这时候RadioGroup就不能知足这样的功能设计了;基于此,我写了这个MultiLineRadioGroup而且开源出来;git
一、程序界面效果图github
二、功能接口app
在Api开发上,可以用到的功能及我能想到的,基本都已经添加完毕;具体以下:ide
三、Demo连接地址布局
https://github.com/a284628487/MultiLineRadioGroupspa
2、项目分析设计
一、基于上面的功能设计,为了设计方便,添加了一些自定义属性;code
<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>
上面的几个自定义属性分别表示orm
二、在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能够根据你的需求设置它的不一样状态下的样式;
三、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)、其它方法
这些方法都是根据经常使用或者可能用到的方法来进行实现的,比较简单,就再也不贴出代码,上面的Demo连接中都有;
Over!