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官网文档中给出了这样的解释:
工作中,我们对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


猜你喜欢
- 本文实例讲述了Java针对封装数组的简单复杂度分析方法。分享给大家供大家参考,具体如下:完成了数组的封装之后我们还需对其进行复杂度分析:此处
- 本文实例讲述了Java开发之spring security实现基于MongoDB的认证功能。分享给大家供大家参考,具体如下:spring s
- 本文实例讲述了C#实现回文检测的方法。分享给大家供大家参考。具体分析如下:回文:称正读和反读都相同的字符序列为“回文”,如“abba”、“a
- 导出Excel的框架有很多种,POI相对来说比较老了,很多Excel框架底层都是POI、有EasyPoi、EasyExcel、包括Hutoo
- Java中有两种处理异常的方式,分别是用throws抛出异常、用try、catch捕获异常。try-catch在Javatry-catch语
- package cn.response;import java.awt.Color;import java.awt.Font;import
- File存储(内部存储)一旦程序在设备安装后,data/data/包名/ 即为内部存储空间,对外保密。Context提供了2个方法来打开输入
- 1、使用排序2、原理事实上Collections.sort方法底层就是调用的array.sort方法,而且不论是Collections.so
- 目录引言命名规则代码排版1.代码缩进对齐2.遇到分号换行3.大括号、括号等成对出现4.加上注释Java注释注释的作用注释的3种类型给代码加上
- 最近一段时间在研究OAuth2的使用,想整个单点登录,从网上找了很多demo都没有实施成功,也许是因为压根就不懂OAuth2的原理导致。有那
- Java集合删除元素ArrayList实例详解AbstractCollection集合类中有一个remove方法,该方法为了适配多种不同的集
- 1、任何的高并发,请求总是会有一个顺序的2、java的队列的数据结构是先进先出的取值顺序3、BlockingQueue类(线程安全)(使用方
- 下载:1.在spring-mvc中配置(用于100M以下的文件下载)<bean class="org.springframe
- 前序格式<meta-data android:name="weather" android:value="
- 前言最近在网上看到一篇文章,里面说到:List<T>.FindAll的效率竟然比for循环还差,下面是文章的截图:我在上文代码基
- 本人亲测,在使用IDEA使用Maven模板创建项目或者在当前项目中New Project,Maven的以下三个配置参数会重置使用C:\Use
- 1. 线程转储简介线程转储(Thread Dump)就是JVM中所有线程状态信息的一次快照。线程转储一般使用文本格式, 可以将其保存到文本文
- 1)页面跳转 直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转。 返回带有前缀的字符串:转发:
- 对于Android View的测量,我们一句话总结为:
- 背景项目中用到了多数据源,不同的数据源根据业务不同配置在不同的工程中,由maven来统一聚合。但是前几天在开发过程中突然发现项目前台工程的事