Android自定义RadioGroupX实现多行多列布局

前言

今天在做新需求的时候,活动有多个类型可以选择,UI给的设计图为多行多列排版,且单项选择,细细想来,谷歌并没有为我们提供类似的控件,初步设想使用RecyclerView实现多行多列布局,然后再用代码控制逻辑部分,总感觉不太稳妥,又想到让UI小姐姐重新设计一番?感觉也不太稳妥,这样UI小姐姐就会认为我菜,为了不让別人觉得我菜,干脆自定义RadioGroupX实现多行多列布局。

思考

在工作中,面对一个功能,首先想到的是应该怎样实现完成它,然后再考虑究竟怎样实现才更优雅。正如前面提到,实现这种需求是可以用多种姿势完成,比如使用RecyclerView,或者使用ConstraintLayout装有多个TextView的布局,用代码控制选项逻辑,在思考一番后,总感觉太生硬,不太优雅,代码量多也许容易出bug。于是通过阅读谷歌为我们提供的RadioGroup源码得出一些灵感,阅读源码往往能使自己大彻大悟。比如在RadioGroup中为什么只支持单行多列或者多行单列布局,主要原因是因为RadioGroup extends LineLayout,所以導致了很多局限性。看到这里突然联想到GridView支持多行多列布局,于是乎,模仿RadioGroup源码自定义一个容器继承GridView。

初识OnHierarchyChangeListener接口

OnHierarchyChangeListener接口位于ViewGroup java文件中,在日常工作中,几乎不会用到,在developer官网文档中给出了这样的解释:

工作中,我们对addView()和RemoveView()这两个方法一定不陌生,其实我们在操作这两个方法的时候就会触发OnHierarchyChangeListener接口中的java void onChildViewAdded(View parent, View child)java void onChildViewRemoved(View parent, View child);两个方法回调,源码中也给了详细解释。我们可以直接在源码中阅读注释加以理解。

参照RadioGroup源码定义内部类

PassThroughHierarchyChangeListener

private inner class PassThroughHierarchyChangeListener :
        OnHierarchyChangeListener {
        private val mOnHierarchyChangeListener: OnHierarchyChangeListener? = null
        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        override fun onChildViewAdded(
            parent: View,
            child: View
        ) {
            if (parent == this@MultiLineRadioGroup && child is RadioButton) {
                var id = child.getId()
                // generates an id if it's missing
                if (id == View.NO_ID) {
                    id = View.generateViewId()
                    child.setId(id)
                }
                child.setOnCheckedChangeListener(
                    mChildOnCheckedChangeListener
                )
            }
            mOnHierarchyChangeListener?.onChildViewAdded(parent, child)
        }

        /**
         * {@inheritDoc}
         */
        override fun onChildViewRemoved(parent: View, child: View) {
            if (parent == this@MultiLineRadioGroup && child is RadioButton) {
                child.setOnCheckedChangeListener(null)
            }
            mOnHierarchyChangeListener?.onChildViewRemoved(parent, child)
        }
    }

在上面重写kotlin onChildViewAdded( parent: View, child: View )kotlinonChildViewRemoved(parent: View, child: View)两个方法,我们着重关注onChildViewAdded方法,当我们在容器中添加子控件时,有多少个子孩子该方法就会触发多少次,我们在此动态设置子View的选中事件监听。

定义CheckedStateTracker实现

CompoundButton.OnCheckedChangeListener接口

private inner  class CheckedStateTracker : CompoundButton.OnCheckedChangeListener {
        override fun onCheckedChanged(
            buttonView: CompoundButton,
            isChecked: Boolean
        ) { // prevents from infinite recursion
            if (mProtectFromCheckedChange) {
                return
            }
            mProtectFromCheckedChange = true
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false)
            }
            mProtectFromCheckedChange = false
            val id = buttonView.id
            setCheckedId(id)
        }
    }

在onCheckedChanged方法中处理子View也就是RadioButton的选中与取消事件,通过以上两个步骤,基本完成了,View选中事件监听和事件处理逻辑

RadioGroupX完整代码

class RadioGroupX: GridLayout {

    private var mProtectFromCheckedChange = false
    var mCheckedId = -1

    private val mChildOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener = CheckedStateTracker()
    private val mPassThroughListener: PassThroughHierarchyChangeListener = PassThroughHierarchyChangeListener()
    private var mOnCheckedChangeListener: OnCheckedChangeListener? = null

    constructor(context: Context?): this(context, null)

    constructor(context: Context?, attrs: AttributeSet?): this(context, attrs, 0)

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr)

    init {
        super.setOnHierarchyChangeListener(mPassThroughListener)
    }

    override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
        if (child is RadioButton) {
            if (child.isChecked) {
                mProtectFromCheckedChange = true
                if (mCheckedId != -1) {
                    setCheckedStateForView(mCheckedId, false)
                }
                mProtectFromCheckedChange = false
                setCheckedId(child.id)
            }
        }
        super.addView(child, index, params)
    }

    fun check(@IdRes id: Int) { // don't even bother
        if (id != -1 && id == mCheckedId) {
            return
        }
        if (mCheckedId != -1) {
            setCheckedStateForView(mCheckedId, false)
        }
        if (id != -1) {
            setCheckedStateForView(id, true)
        }
        setCheckedId(id)
    }

    private fun setCheckedId(@IdRes id: Int) {
        val changed = id != mCheckedId
        mCheckedId = id
        mOnCheckedChangeListener?.onCheckedChanged(this, mCheckedId)
//        if (changed) {
//            val afm: AutofillManager = mContext.getSystemService(
//                AutofillManager::class.java
//            )
//            afm?.notifyValueChanged(this)
//        }
    }

    private fun setCheckedStateForView(viewId: Int, checked: Boolean) {
        val checkedView = findViewById<View>(viewId)
        if (checkedView != null && checkedView is RadioButton) {
            checkedView.isChecked = checked
        }
    }

    private inner  class CheckedStateTracker : CompoundButton.OnCheckedChangeListener {
        override fun onCheckedChanged(
            buttonView: CompoundButton,
            isChecked: Boolean
        ) { // prevents from infinite recursion
            if (mProtectFromCheckedChange) {
                return
            }
            mProtectFromCheckedChange = true
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false)
            }
            mProtectFromCheckedChange = false
            val id = buttonView.id
            setCheckedId(id)
        }
    }

    fun setOnCheckedChangeListener(listener: OnCheckedChangeListener) {
        mOnCheckedChangeListener = listener
    }

    interface OnCheckedChangeListener {
        fun onCheckedChanged(group: RadioGroupX?, @IdRes checkedId: Int)
    }

    private inner class PassThroughHierarchyChangeListener :
        OnHierarchyChangeListener {
        private val mOnHierarchyChangeListener: OnHierarchyChangeListener? = null
        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        override fun onChildViewAdded(
            parent: View,
            child: View
        ) {
            if (parent == this@RadioGroupX && child is RadioButton) {
                var id = child.getId()
                // generates an id if it's missing
                if (id == View.NO_ID) {
                    id = View.generateViewId()
                    child.setId(id)
                }
                child.setOnCheckedChangeListener(
                    mChildOnCheckedChangeListener
                )
            }
            mOnHierarchyChangeListener?.onChildViewAdded(parent, child)
        }

        /**
         * {@inheritDoc}
         */
        override fun onChildViewRemoved(parent: View, child: View) {
            if (parent == this@RadioGroupX && child is RadioButton) {
                child.setOnCheckedChangeListener(null)
            }
            mOnHierarchyChangeListener?.onChildViewRemoved(parent, child)
        }
    }

}

xml中使用

<com.example.multilineradiogroupdemo.RadioGroupX
            android:layout_width="match_parent"
            android:columnCount="3"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/line">

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="数学" />

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="语文" />

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="地理" />

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="生物" />

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="计算机" />

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="化学" />

</com.example.multilineradiogroupdemo.RadioGroupX>

activity事件处理部分和使用RadioGroup原理一样,照搬即可。

总结

通过上面短短几步,我们基本完成了需求中的排版问题,如果不阅读借鉴源码中的思路,我想我是很难写出来,至少不会在很短时间就完成需求设计,所以工作我应该做到更多的阅读源码,了解源码中的设计思路和思想,这样自己才能有所提高。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android控件gridview实现单行多列横向滚动效果

    本文实例为大家分享了安卓实现单行多列横向滚动,供大家参考,具体内容如下 <GridLayout android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="1" > <HorizontalScrollView android:layout_width="match_parent" a

  • android 修改launcher行数和列数的方法

    android 修改launcher行数和列数 Launcher3桌面的行数和列数都是在InvariantDeviceProfile.java和DeviceProfile.java中动态计算的,xml中无法配置. Note:L版本无InvariantDeviceProfile.java,是DynamicGrid.java,但是计算方法都是一样的. InvariantDeviceProfile中用InvariantDeviceProfile来配置各种屏幕的手机桌面.如下: InvariantDev

  • Android自定义ViewGroup多行多列效果

    本文实例为大家分享了Android自定义ViewGroup多行多列的具体代码,供大家参考,具体内容如下 先看下效果图 每行两个子孩子 每行一个子孩子 实现思路 自定义viewGroup,实现测量和布局,使控件适应业务场景. 测量 根据父控件的宽度,平均分给每个子孩子固定的宽度.高度就是行数乘以一个子孩子的高度,再加上空隙的高度. 根据子孩子个数计算行数 val rows = if (childCount % perLineChild == 0) { childCount / perLineChi

  • Android中控件GridView实现设置行列分割线的方法示例

    前言 虽然Aandroid目前已经有RecyclerView了.非常强大的一个View.可以直接控制成ListView以及GridView等.而且画框线也比较方便.但是呢在很多情况下我们不得不仍然使用GridView来实现布局.那么在这个时候我们又要怎么来对GridViw进行画框线呢.下面将提供两种实现方式.大家可以选择一下 一.设置垂直.横向间距.通过GRIDVIEW和ITEM的背景色来实现 1.设置GridView背景色 2.设置水平和竖直方向间隔:android:horizontalSpa

  • Android自定义RadioGroupX实现多行多列布局

    前言 今天在做新需求的时候,活动有多个类型可以选择,UI给的设计图为多行多列排版,且单项选择,细细想来,谷歌并没有为我们提供类似的控件,初步设想使用RecyclerView实现多行多列布局,然后再用代码控制逻辑部分,总感觉不太稳妥,又想到让UI小姐姐重新设计一番?感觉也不太稳妥,这样UI小姐姐就会认为我菜,为了不让別人觉得我菜,干脆自定义RadioGroupX实现多行多列布局. 思考 在工作中,面对一个功能,首先想到的是应该怎样实现完成它,然后再考虑究竟怎样实现才更优雅.正如前面提到,实现这种需

  • Android自定义ViewGroup之实现FlowLayout流式布局

    整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38352503/  一.FlowLayout介绍  所谓FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行.有点像所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局.Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图: git

  • Android 自定义View实现任意布局的RadioGroup效果

    前言 RadioGroup是继承LinearLayout,只支持横向或者竖向两种布局.所以在某些情况,比如多行多列布局,RadioGroup就并不适用 . 本篇文章通过继承RelativeLayout实现自定义RadioGroup,实现RadioButton的任意布局.效果图如下: 代码(RelativeRadioGroup) /** * Author : BlackHao * Time : 2018/10/26 10:46 * Description : 自定义 RadioGroup */ p

  • Android自定义ViewGroup实现标签流容器FlowLayout

    本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout,但不影响我对其的学习.和往常一样,主要还是想总结一下自定义ViewGroup的开发过程以及一些需要注意的地方. 按照惯例,我们先来看看效果图 一.写代码之前,有几个是问题是我们先要弄清楚的: 1.什么是ViewGroup:从名字上来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许

  • Android自定义View实现仿GitHub的提交活跃表格

    说明 本文可能需要一些基础知识点,如Canvas,Paint,Path,Rect等类的基本使用,建议不熟悉的同学可以学习GcsSloop安卓自定义View教程目录,会帮助很大. 上图就是github的提交表格,直观来看可以分为几个部分进行绘制: (1)各个月份的小方格子,并且色彩根据提交次数变化,由浅到深 (2)右下边的颜色标志,我们右对齐就可以了 (3)左边的星期,原图是从周日画到周六,我们从周一画到周日 (4)上面的月份,我们只画出1-12月 (5)点击时候弹出当天的提交情况,由一个小三角和

  • Android自定义UI手势密码改进版源码下载

    在之前文章的铺垫下,再为大家分享一篇:Android手势密码,附源码下载,不要错过. 源码下载:http://xiazai.jb51.net/201610/yuanma/androidLock(jb51.net).rar 先看第一张图片的布局文件 activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://sc

  • Android自定义view实现日历打卡签到

    本文实例为大家分享了Android自定义view实现日历打卡签到的具体代码,供大家参考,具体内容如下 1.说明 自己写一个view实现每天签到的功能,设置背景图片 源码下载 2.效果图 3.主界面 package com.example.myapplication30; import androidx.appcompat.app.AppCompatActivity; import android.annotation.SuppressLint; import android.os.Bundle;

  • Android自定义Gradle插件的详细过程

    一.Gradle 我们知道在我们现在使用Android Stduio开发Android项目的时候,Android Studio是基于Gradle来帮助我们构建,管理项目的. Gradle:Gradle是一个项目构建工具,用来帮助我们管理项目的依赖.打包.发布.部署等工作. Gradle是通过如build.gradle这种gradle脚本来进行项目构建的,所以我们对项目的构建配置都是可以写在gradle构建脚本中. gradle构建脚本使用的是Groovy语言,Groovy语言也是一种jvm语言,

  • Android自定义view绘制表格的方法

    本文实例为大家分享了Android自定义view绘制表格的具体代码,供大家参考,具体内容如下 先上效果图 平时很少有这样的表格需求,不过第一想法就是自定义view绘制表格,事实上我确实是用的canvas来绘制的,整个过程看似复杂,实为简单,计算好各个点的坐标后事情就完成一半了.不废话show code import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; imp

随机推荐