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原理一样,照搬即可。
总结
通过上面短短几步,我们基本完成了需求中的排版问题,如果不阅读借鉴源码中的思路,我想我是很难写出来,至少不会在很短时间就完成需求设计,所以工作我应该做到更多的阅读源码,了解源码中的设计思路和思想,这样自己才能有所提高。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。