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

作者:辉涛 时间:2023-03-27 17:19:47 

前言

今天在做新需求的时候,活动有多个类型可以选择,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原理一样,照搬即可。

总结

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

来源:https://blog.csdn.net/zhuhuitao_struggle/article/details/114155700

标签:Android,多行多列,布局
0
投稿

猜你喜欢

  • 使用C#调用百度地图并实现坐标点的设置以及读取示例

    2023-01-06 02:32:57
  • JavaWeb利用struts实现文件下载时改变文件名称

    2023-10-24 11:26:53
  • java中的Object类的toSpring()方法

    2022-08-30 12:36:03
  • 基于StreamRead和StreamWriter的使用(实例讲解)

    2022-09-11 22:12:36
  • Java毕业设计实战项目之在线服装销售商城系统的实现流程

    2023-03-27 00:15:31
  • httpwebreqeust读取httponly的cookie方法

    2022-04-19 19:47:05
  • 有关tomcat内存溢出的完美解决方法

    2023-09-18 09:02:25
  • Java解决约瑟夫问题代码实例

    2023-09-20 19:17:02
  • java解析Excel的方法(xls、xlsx两种格式)

    2021-10-12 16:29:39
  • Android 创建与解析XML(五)——详解Dom4j方式

    2022-06-15 17:08:51
  • 浅谈java并发之计数器CountDownLatch

    2023-03-21 23:42:59
  • Windows编写jar启动脚本和关闭脚本的操作方法

    2021-05-28 04:36:58
  • Java如何重写object类的equals方法详解

    2023-09-01 15:54:57
  • java中类加载与双亲委派机制详解

    2023-12-07 06:34:53
  • Intellij IDEA中如何查看maven项目中所有jar包的依赖关系图

    2023-02-01 23:34:03
  • Java8深入学习之熟透Optional

    2023-08-24 21:27:54
  • SpringBoot+Prometheus+Grafana实现应用监控和报警的详细步骤

    2023-10-02 06:40:26
  • C#使用反射机制实现延迟绑定

    2021-06-13 22:22:42
  • Dubbo Consumer引用服务示例代码详解

    2022-04-26 03:56:44
  • SpringBoot参数校验Validator框架详解

    2023-09-22 07:08:40
  • asp之家 软件编程 m.aspxhome.com