欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-09 11:46:14
前言今天在做新需求的时候,活动有多个类型可以选择,ui给的设计图为多行多列排版,且单项选择,细细想来,谷歌并没有为我们提供类似的控件,初步设想使用recyclerview实现多行多列布局,然后再用代码...

前言

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

思考

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

初识onhierarchychangelistener接口

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

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

工作中,我们对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原理一样,照搬即可。

总结

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

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