Android自定義RadioGroupX實(shí)現(xiàn)多行多列布局
前言
今天在做新需求的時(shí)候,活動(dòng)有多個(gè)類型可以選擇,UI給的設(shè)計(jì)圖為多行多列排版,且單項(xiàng)選擇,細(xì)細(xì)想來,谷歌并沒有為我們提供類似的控件,初步設(shè)想使用RecyclerView實(shí)現(xiàn)多行多列布局,然后再用代碼控制邏輯部分,總感覺不太穩(wěn)妥,又想到讓UI小姐姐重新設(shè)計(jì)一番?感覺也不太穩(wěn)妥,這樣UI小姐姐就會(huì)認(rèn)為我菜,為了不讓別人覺得我菜,干脆自定義RadioGroupX實(shí)現(xiàn)多行多列布局。
思考
在工作中,面對(duì)一個(gè)功能,首先想到的是應(yīng)該怎樣實(shí)現(xiàn)完成它,然后再考慮究竟怎樣實(shí)現(xiàn)才更優(yōu)雅。正如前面提到,實(shí)現(xiàn)這種需求是可以用多種姿勢(shì)完成,比如使用RecyclerView,或者使用ConstraintLayout裝有多個(gè)TextView的布局,用代碼控制選項(xiàng)邏輯,在思考一番后,總感覺太生硬,不太優(yōu)雅,代碼量多也許容易出bug。于是通過閱讀谷歌為我們提供的RadioGroup源碼得出一些靈感,閱讀源碼往往能使自己大徹大悟。比如在RadioGroup中為什么只支持單行多列或者多行單列布局,主要原因是因?yàn)镽adioGroup extends LineLayout,所以導(dǎo)致了很多局限性。看到這里突然聯(lián)想到GridView支持多行多列布局,于是乎,模仿RadioGroup源碼自定義一個(gè)容器繼承GridView。
初識(shí)OnHierarchyChangeListener接口
OnHierarchyChangeListener接口位于ViewGroup java文件中,在日常工作中,幾乎不會(huì)用到,在developer官網(wǎng)文檔中給出了這樣的解釋:
工作中,我們對(duì)addView()和RemoveView()這兩個(gè)方法一定不陌生,其實(shí)我們?cè)诓僮鬟@兩個(gè)方法的時(shí)候就會(huì)觸發(fā)OnHierarchyChangeListener接口中的java void onChildViewAdded(View parent, View child)和java void onChildViewRemoved(View parent, View child);兩個(gè)方法回調(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)兩個(gè)方法,我們著重關(guān)注onChildViewAdded方法,當(dāng)我們?cè)谌萜髦刑砑幼涌丶r(shí),有多少個(gè)子孩子該方法就會(huì)觸發(fā)多少次,我們?cè)诖藙?dòng)態(tài)設(shè)置子View的選中事件監(jiān)聽。
定義CheckedStateTracker實(shí)現(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的選中與取消事件,通過以上兩個(gè)步驟,基本完成了,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="語(yǔ)文" /> <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ì)算機(jī)" /> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="化學(xué)" /> </com.example.multilineradiogroupdemo.RadioGroupX>
activity事件處理部分和使用RadioGroup原理一樣,照搬即可。
總結(jié)
通過上面短短幾步,我們基本完成了需求中的排版問題,如果不閱讀借鑒源碼中的思路,我想我是很難寫出來,至少不會(huì)在很短時(shí)間就完成需求設(shè)計(jì),所以工作我應(yīng)該做到更多的閱讀源碼,了解源碼中的設(shè)計(jì)思路和思想,這樣自己才能有所提高。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter彈性布局Flex水平排列Row垂直排列Column使用示例
這篇文章主要為大家介紹了Flutter彈性布局Flex水平排列Row垂直排列Column使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Android 開發(fā)之旅:詳解view的幾種布局方式及實(shí)踐
這篇文章主要介紹了Android 開發(fā)之旅:詳解view的幾種布局方式及實(shí)踐,具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12Android實(shí)現(xiàn)超級(jí)棒的沉浸式體驗(yàn)教程
這篇文章主要給大家介紹了關(guān)于Android如何實(shí)現(xiàn)超級(jí)棒的沉浸式體驗(yàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Android具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11Android編程監(jiān)聽APK安裝與刪除等過程的方法
這篇文章主要介紹了Android編程監(jiān)聽APK安裝與刪除等過程的方法,涉及Android事件監(jiān)聽、權(quán)限控制、廣播操作等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10Android測(cè)量每秒幀數(shù)Frames Per Second (FPS)的方法
這篇文章主要介紹了Android測(cè)量每秒幀數(shù)Frames Per Second (FPS)的方法,涉及Android針對(duì)多媒體文件屬性操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android檢測(cè)Activity或者Service是否運(yùn)行的方法
下面小編就為大家分享一篇Android檢測(cè)Activity或者Service是否運(yùn)行的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03Android 支付寶支付、微信支付、銀聯(lián)支付 整合第三方支付接入方法(后臺(tái)訂單支付API設(shè)計(jì))
這篇文章主要介紹了Android 支付寶支付、微信支付、銀聯(lián)支付 整合第三方支付接入方法(后臺(tái)訂單支付API設(shè)計(jì))的相關(guān)資料,需要的朋友可以參考下2016-11-11