Android自定義RadioGroupX實現(xiàn)多行多列布局
前言
今天在做新需求的時候,活動有多個類型可以選擇,UI給的設(shè)計圖為多行多列排版,且單項選擇,細(xì)細(xì)想來,谷歌并沒有為我們提供類似的控件,初步設(shè)想使用RecyclerView實現(xiàn)多行多列布局,然后再用代碼控制邏輯部分,總感覺不太穩(wěn)妥,又想到讓UI小姐姐重新設(shè)計一番?感覺也不太穩(wěn)妥,這樣UI小姐姐就會認(rèn)為我菜,為了不讓別人覺得我菜,干脆自定義RadioGroupX實現(xiàn)多行多列布局。
思考
在工作中,面對一個功能,首先想到的是應(yīng)該怎樣實現(xiàn)完成它,然后再考慮究竟怎樣實現(xiàn)才更優(yōu)雅。正如前面提到,實現(xiàn)這種需求是可以用多種姿勢完成,比如使用RecyclerView,或者使用ConstraintLayout裝有多個TextView的布局,用代碼控制選項邏輯,在思考一番后,總感覺太生硬,不太優(yōu)雅,代碼量多也許容易出bug。于是通過閱讀谷歌為我們提供的RadioGroup源碼得出一些靈感,閱讀源碼往往能使自己大徹大悟。比如在RadioGroup中為什么只支持單行多列或者多行單列布局,主要原因是因為RadioGroup extends LineLayout,所以導(dǎo)致了很多局限性??吹竭@里突然聯(lián)想到GridView支持多行多列布局,于是乎,模仿RadioGroup源碼自定義一個容器繼承GridView。
初識OnHierarchyChangeListener接口
OnHierarchyChangeListener接口位于ViewGroup java文件中,在日常工作中,幾乎不會用到,在developer官網(wǎng)文檔中給出了這樣的解釋:

工作中,我們對addView()和RemoveView()這兩個方法一定不陌生,其實我們在操作這兩個方法的時候就會觸發(fā)OnHierarchyChangeListener接口中的java void onChildViewAdded(View parent, View child)和java void onChildViewRemoved(View parent, View child);兩個方法回調(diào),源碼中也給了詳細(xì)解釋。我們可以直接在源碼中閱讀注釋加以理解。
參照RadioGroup源碼定義內(nèi)部類
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)兩個方法,我們著重關(guān)注onChildViewAdded方法,當(dāng)我們在容器中添加子控件時,有多少個子孩子該方法就會觸發(fā)多少次,我們在此動態(tài)設(shè)置子View的選中事件監(jiān)聽。
定義CheckedStateTracker實現(xiàn)
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選中事件監(jiān)聽和事件處理邏輯
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="數(shù)學(xué)" />
<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="計算機(jī)" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="化學(xué)" />
</com.example.multilineradiogroupdemo.RadioGroupX>
activity事件處理部分和使用RadioGroup原理一樣,照搬即可。
總結(jié)
通過上面短短幾步,我們基本完成了需求中的排版問題,如果不閱讀借鑒源碼中的思路,我想我是很難寫出來,至少不會在很短時間就完成需求設(shè)計,所以工作我應(yīng)該做到更多的閱讀源碼,了解源碼中的設(shè)計思路和思想,這樣自己才能有所提高。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android開發(fā)之選項卡功能的實現(xiàn)方法示例
這篇文章主要介紹了Android開發(fā)之選項卡功能的實現(xiàn)方法,結(jié)合實例形式分析了Android選項卡功能的原理、實現(xiàn)技巧與相關(guān)注意事項,需要的朋友可以參考下2017-06-06
Android實現(xiàn)基于滑動的SQLite數(shù)據(jù)分頁加載技術(shù)(附demo源碼下載)
這篇文章主要介紹了Android實現(xiàn)基于滑動的SQLite數(shù)據(jù)分頁加載技術(shù),涉及Android針對SQLite數(shù)據(jù)的讀取及查詢結(jié)果的分頁顯示功能相關(guān)實現(xiàn)技巧,末尾還附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-07-07
基于Flutter實現(xiàn)短信驗證碼監(jiān)控與轉(zhuǎn)發(fā)
這篇文章主要為大家詳細(xì)介紹了如何基于Flutter實現(xiàn)短信驗證碼監(jiān)控與轉(zhuǎn)發(fā)功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03
Android音視頻開發(fā)之MediaExtactor使用教程
MediaExtactor在Android音視頻開發(fā)中負(fù)責(zé)提取音視頻信息和數(shù)據(jù)流的功能,可以通過該類實現(xiàn)從多媒體文件中剝離得到音頻和視頻的能力。本文將詳細(xì)為大家介紹一下它的使用,感興趣的可以了解一下2022-04-04
Android自定義scrollView實現(xiàn)頂部圖片下拉放大
這篇文章主要為大家詳細(xì)介紹了Android自定義scrollView實現(xiàn)頂部圖片下拉放大,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
詳解Android業(yè)務(wù)組件化之URL Schema使用
這篇文章主要為大家詳細(xì)介紹了Android業(yè)務(wù)組件化之URL Schema使用,感興趣的小伙伴們可以參考一下2016-09-09

