Android 自定義View實現多行RadioGroup (MultiLineRadioGroup)

1、項目概況android

  咱們都知道RadioGroup能夠實現選擇框,但它有一個侷限性,因爲它是繼承自LinearLayout的,因此只能有一個方向,橫向或者縱向;但有時候僅一行的RadioGroup並不能知足實際的需求,好比在一行的寬度下顯示不完全部的選項,設計上又不容許左右滑動,這時候RadioGroup就不能知足這樣的功能設計了;基於此,我寫了這個MultiLineRadioGroup而且開源出來;git

一、程序界面效果圖github

 

 

二、功能接口app

在Api開發上,可以用到的功能及我能想到的,基本都已經添加完畢;具體以下:ide

  • child選項添加,刪除
  • child選項選中,取消選中
  • child對齊方式(左|中|右)
  • child行間距,左右間距
  • 設置選擇模式(單選|多選)
  • 獲取已選選項
  • child選擇狀態的監聽回調查

三、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

  • child水平間距
  • child上下間距
  • child對應的layout佈局文件(後面會講到,此屬性必須配置)
  • 初始元素個數
  • 初始元素值列表
  • 選擇模式(單選|多選)
  • child對齊方式

二、在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)、其它方法

  • 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!

相關文章
相關標籤/搜索