自定义一个不一样的 RadioGroup

前言

其实好久之前就想写这篇文章了,然鹅懒癌发作就一直没写。

Tips:本篇博客的源码有参考群里毒药大佬写的代码并加以修改(药佬不爱写技术博客,那就我来写好啦~)。

缘起(为什么要自定义 RadioGroup?)

官方的 RadioGroup 其是 LinearLayout 的子类,然后需要被单选的控件必须是 RadioButtonRadioButton 的子类才有效果(源码里有使用 instanceof 进行判断)。


官方 RadioGroup 控件的缺点:

  • 由于是 LinearLayout 的子类,所以只能设置 HORIZONTAL(横向) 或者 VERTICAL。

(纵向)。

  • 子类必须是 RadioButton ,有些样式用 RadioButton 做起来不是很爽。

实现思路

实现非常简单粗暴

  • 遍历子 View 设置点击事件
  • 调用子 View 点击事件的时候清空所有的选中
  • 设置当前被点击的子 View 为选中状态

缘灭

引入依赖

1
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'

代码实现

思路有了,于是我们可以顺手写出如下代码


BaseRadioGroup.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.cqautotest.sunnybeach.widget

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.Group

/**
* author : A Lonely Cat
* github : https://github.com/anjiemo/SunnyBeach
* time : 2022/10/05
* desc : RadioGroup 基类封装
*/
open class BaseRadioGroup @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Group(context, attrs) {

override fun setOnClickListener(l: OnClickListener?) {
super.setOnClickListener(l)
getViews()?.forEach { it.setOnClickListener(l) }
}

open fun getViews() = (parent as? ViewGroup)?.let { parent -> referencedIds.map { parent.findViewById<View?>(it) } }

open fun dispatchSelect(isSelected: Boolean) {
getViews()?.forEach { it?.isSelected = isSelected }
}
}


Tips:这里有一个小细节,我们直接继承了 androidx.constraintlayout.widget 包下的 Group 控件,这样我们就可以方便的在 xml 布局里指定 “ids” 了(后面会说)。

app/main/res/values/attrs.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="RadioGroup">
<!-- 默认选中的View id -->
<attr name="defaultSelectId" format="reference" />
</declare-styleable>

</resources>

RadioGroup.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package cn.cqautotest.sunnybeach.widget

import android.content.Context
import android.util.AttributeSet
import android.view.View
import cn.cqautotest.sunnybeach.R

/**
* author : A Lonely Cat
* github : https://github.com/anjiemo/SunnyBeach
* time : 2022/10/05
* desc : RadioGroup 单选按钮组
*/
class RadioGroup @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : BaseRadioGroup(context, attrs) {

// 默认选中的 id
private val defaultSelectId: Int

// 是否初始化
private var isInitialization = false

// 选中的监听
var onSelected: ((View) -> Unit) = {}

init {
val attributes = context.obtainStyledAttributes(attrs, R.styleable.RadioGroup)
defaultSelectId = attributes.getResourceId(R.styleable.RadioGroup_defaultSelectId, View.NO_ID)
attributes.recycle()
}

override fun applyLayoutFeatures() {
super.applyLayoutFeatures()
if (defaultSelectId != View.NO_ID && !isInitialization) {
selected(defaultSelectId)
}
getViews()?.forEach {
it?.setOnClickListener { _ ->
dispatchSelect(false)
it.isSelected = true
onSelected.invoke(it)
}
}
}

/**
* 选中某个 View,参数:id
*/
fun selected(id: Int) {
dispatchSelect(false)
getViews()?.find { it.id == id }?.also { it.isSelected = true }?.also(onSelected)
isInitialization = true
}
}

食用方式

xml 布局

1
2
3
4
5
6
<cn.cqautotest.sunnybeach.widget.RadioGroup
android:id="@+id/radio_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_iqiyi,btn_submit,iv_cover,ll_container,fl_container" />


只需要参与单选的控件在与自定义 RadioGroup 同一个 ViewGroup 下即可(因为我们是在当前自定义 RadioGroup 控件的父类下查找的控件)实现单选效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<cn.cqautotest.sunnybeach.widget.RadioGroup
android:id="@+id/radio_group"
android:layout_width="0px"
android:layout_height="0px"
app:constraint_referenced_ids="tv_squad_leader,tv_vice_monitor,tv_commissary_in_charge_of_studies,tv_commissary_in_charge_of_general_affairs,tv_commissary_in_charge_of_organization,tv_commissary_in_charge_of_publicity,tv_commissary_in_charge_of_sports,iv_login_qq"
app:defaultSelectId="@id/tv_squad_leader" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_squad_leader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="班长"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_vice_monitor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="副班长"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

<com.hjq.shape.view.ShapeImageView
android:id="@+id/iv_login_qq"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/login_qq_ic"
app:shape_solidColor="@color/pink"
app:shape_solidSelectedColor="@color/follow_btn_text_normal_color" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_commissary_in_charge_of_studies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="学习委员"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_commissary_in_charge_of_general_affairs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="生活委员"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_commissary_in_charge_of_organization"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="组织委员"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_commissary_in_charge_of_publicity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="宣传委员"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_commissary_in_charge_of_sports"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:text="体育委员"
app:shape_solidColor="@color/white"
app:shape_solidSelectedColor="#F19C4A"
app:shape_textSelectedColor="@color/white" />

</LinearLayout>

kotlin 代码

监听选中

1
2
3
 radioGroup.onSelected = { view ->
// do something...
}

设置选中

1
radioGroup.selected(R.id.btn_submit)

总结

优点

  • 不限制参与单选的控件类型,你可以使用 Button、TextView、ImageView、LinearLayout、FrameLayout…
  • 自定义的 RadioGroup 只需要当成一个普通的 View(Gone 状态) 放在参与单选的控件的共同 ViewGroup 中即可

缺点

  • 由于继承自 androidx.constraintlayout.widget 包下的 Group 控件,所以需要引入约束布局的包。

RadioButton + RadioGroup 实现的单选效果,相信大家都很熟啦~


而且由于灵活性比较强,这里就不贴图啦。

还是补充一下图片吧~

图片描述

图片描述

结语

好啦,就写到这里吧 ,如果你想看更多的项目代码,请查看我在 Github 上的 阳光沙滩APP项目 ,你也可以在这里点击下载我写的 阳光沙滩APP客户端 进行体验。


如果对你有帮助的话,欢迎一键三连+关注哦~


自定义一个不一样的 RadioGroup
https://blog.funkt.cn/2023/02/06/1622630217941180417/
作者
anjiemo
发布于
2023年2月6日
许可协议
未经作者授权,禁止转载