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

  • 相关阅读:
    Jar包管理规范
    Base64编码原理与应用
    MySQL 5.7.14安装说明,解决服务无法启动
    idea注册
    Oracle 如何对中文字段进行排序
    SVN错误:Attempted to lock an already-locked dir
    排序算法
    设计模式
    分层
    阿里云
  • 原文地址:https://www.cnblogs.com/a284628487/p/4494669.html
Copyright © 2020-2023  润新知