Android custom View implements multi-line RadioGroup (MultiLineRadioGroup) and android custom view
I. Project Overview
We all know that RadioGroup can implement a selection box, but it has a limitation. Because it inherits from LinearLayout, it can only have one direction, either horizontally or vertically; however, sometimes only one row of RadioGroup cannot meet the actual needs. For example, if you cannot display all the options under the width of one row, you cannot slide between the left and right sides in design, at this time, RadioGroup cannot meet such functional design. Based on this, I wrote this MultiLineRadioGroup and made it open-source;
1. Program Interface
2. functional interfaces
The functions that can be used in Api development and what I can think of have been added. The details are as follows:
- Add or delete the child Option
- Select the child option. deselect the child option.
- Child alignment (left | Middle | right)
- Child row spacing, left and right spacing
- Set the selection mode (single choice | multiple choice)
- Get selected options
- Check the status of the listener selected by the child
3. Demo URL
Https://github.com/a284628487/MultiLineRadioGroup
2. Project Analysis
1. Based on the above functional design, some custom attributes are added for ease of design;
<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>
The preceding custom attributes represent
- Child Horizontal spacing
- Child spacing
- Layout file corresponding to child (this attribute must be configured later)
- Number of initial elements
- Initial element value list
- Select mode (single choice | multiple choice)
- Child alignment
2. Use MultiLineRadioGroup in layout
(1) define an xml file containing MultiLineRadioGroup
<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) define a layout file whose root node is CheckBox and set the file id to the child_layout attribute of MultiLineRadioGroup (Note: This attribute must be set)
<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>
In MultiLineRadiaGroup, the child element of MultiLineRadiaGroup is CheckBox. Therefore, you must specify the child_layout for layout as CheckBox. This CheckBox can set the style in different States according to your needs;
3. Core MultiLineRadioGroup Method Analysis
(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); }
Traverse all the children and call measureChild to measure the child's width and height. Then, compare the accumulated width with the value of getWidth to determine whether a line break is required, record the number of rows to be used;
(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; }
Like onMeasure, The onLayout method also needs to traverse the child. However, the traversal here is not a measurement, but a placement of the child, during placement, the width and height attributes of the Child Elements measured in the onMeasure method must be used;
Traversal may be performed twice. If the child alignment is not Left, the first traversal calculates the gaps in each row, then, the left offset distance of the first child in each row is calculated based on alignment. During the second traversal, the child is layout based on the previously calculated offset distance;
(3) Other methods
- Append (String str) attaches a child;
- Insert (int position, String str) insert child to the specified position;
- GetCheckedValues () | getCheckedItems () to obtain the selected item;
- Remove (int position) deletes the child at the specified position;
- SetItemChecked (int position) selects the child at the specified position;
- SetGravigy (int gravity) sets the child alignment mode;
These methods are implemented based on common or possibly used methods. They are relatively simple and no longer post code, which is available in the above Demo links;
Over!