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

 更新时间:2021年09月02日 13:15:41   作者:辉涛  
这篇文章主要为大家详细介绍了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原理一样,照搬即可。

总结

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

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

相关文章

  • Android开发之选项卡功能的实现方法示例

    Android开发之选项卡功能的实现方法示例

    这篇文章主要介绍了Android开发之选项卡功能的实现方法,结合实例形式分析了Android选项卡功能的原理、实现技巧与相关注意事项,需要的朋友可以参考下
    2017-06-06
  • Android仿制淘宝滚动图文条的示例代码

    Android仿制淘宝滚动图文条的示例代码

    这篇文章主要介绍了Android仿制淘宝滚动图文条的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • 快速搞懂Android口令加密(一)

    快速搞懂Android口令加密(一)

    这篇文章主要介绍了快速搞懂Android口令加密(一)的相关资料,需要的朋友可以参考下
    2016-04-04
  • Android实现基于滑动的SQLite数据分页加载技术(附demo源码下载)

    Android实现基于滑动的SQLite数据分页加载技术(附demo源码下载)

    这篇文章主要介绍了Android实现基于滑动的SQLite数据分页加载技术,涉及Android针对SQLite数据的读取及查询结果的分页显示功能相关实现技巧,末尾还附带demo源码供读者下载参考,需要的朋友可以参考下
    2016-07-07
  • 基于Flutter实现短信验证码监控与转发

    基于Flutter实现短信验证码监控与转发

    这篇文章主要为大家详细介绍了如何基于Flutter实现短信验证码监控与转发功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-03-03
  • Android音视频开发之MediaExtactor使用教程

    Android音视频开发之MediaExtactor使用教程

    MediaExtactor在Android音视频开发中负责提取音视频信息和数据流的功能,可以通过该类实现从多媒体文件中剥离得到音频和视频的能力。本文将详细为大家介绍一下它的使用,感兴趣的可以了解一下
    2022-04-04
  • Android自定义scrollView实现顶部图片下拉放大

    Android自定义scrollView实现顶部图片下拉放大

    这篇文章主要为大家详细介绍了Android自定义scrollView实现顶部图片下拉放大,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • Android编程之通知栏的用法小结

    Android编程之通知栏的用法小结

    这篇文章主要介绍了Android编程之通知栏的用法,结合实例形式总结分析了Android通知栏的相关操作技巧,包括发送、删除通知、自定义布局等操作实现方法,需要的朋友可以参考下
    2017-01-01
  • 详解Android业务组件化之URL Schema使用

    详解Android业务组件化之URL Schema使用

    这篇文章主要为大家详细介绍了Android业务组件化之URL Schema使用,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • 基于Android实现3D翻页效果

    基于Android实现3D翻页效果

    这篇文章主要为大家详细介绍了基于Android实现3D翻页效果的具体代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-06-06

最新评论