最近有一個需求,需要實現(xiàn)單選的功能,類似這個
像這種結(jié)構(gòu)谓着,通過RecyclerView
,FlexBoxLayout
之類的都可以實現(xiàn)。然而相對比較麻煩坛掠,我想通過RadioGroup
來實現(xiàn)赊锚,但是通過RadioGroup
來實現(xiàn)的話也存在一些問題,RadioGroup
本身是繼承于LinearLayout
屉栓,并不能實現(xiàn)以上布局舷蒲。進(jìn)行嵌套的話,則會失去單選的功能友多,這是為什么呢牲平?通過查看源碼的話可以發(fā)現(xiàn),RadioGroup
設(shè)置了OnHierarchyChangeListener
監(jiān)聽(可以檢測到直接子
View
的添加和移除)夷陋。
在監(jiān)聽到添加子View
的時候欠拾,(上圖375行代碼處可見)如果子View
是RadioButton
的話,則添加setONCheckedChangeWidgetListener()
監(jiān)聽骗绕,如果RadioButton
不存在id
的話藐窄,則通過View.generateViewId()
為其設(shè)置一個id
(RadioGroup
內(nèi)部是通過id
來判斷當(dāng)前選中哪個RadioButton
),不是則不進(jìn)行處理酬土。setONCheckedChangeWidgetListener
的作用就是當(dāng)RadioButton
被選中的時候進(jìn)行通知RadioGroup
荆忍,收到通知后,(下圖346行代碼處) RadioGroup
就會進(jìn)行處理撤缴,會調(diào)用setCheckedStateForView()
方法,這個方法會把上一個選中的RadioButton
取消選中狀態(tài),然后調(diào)用351行的 setCheckedId()
方法刹枉,設(shè)置當(dāng)前選中 RadioGroup
的id
,并回調(diào)我們平時設(shè)置的setOnCheckedChangeListener
中的OnCheckedChangeListener
。
這樣我們就明白了RadioGroup
如何實現(xiàn)選中的屈呕,也明白了為什么嵌套后會失去單選效果微宝。
這個時候,我肯定會按照慣例去Google一下有沒有輪子虎眨,然而發(fā)現(xiàn)了很多都是自定義View的蟋软,基本上都是繼承LinearLayout
實現(xiàn)一套類似RadioGroup
的實現(xiàn),這樣會失去原有的RadioGroup
的特性嗽桩,而且也存在一個明顯的問題岳守,就是只能夠在xml
布局中實現(xiàn)互斥,如果在代碼中進(jìn)行動態(tài)添加的話碌冶,則會失去單選的效果湿痢。
于是我想有沒有一個辦法,可以無侵入式的實現(xiàn)RadioGroup
的無限嵌套扑庞,事實證明可以譬重。
我們先來看一下使用方法:
new RadioGroupUtils(rg).supportNest();
對的,就是這么一句話就可以了嫩挤。下面我們對RadioGroupUtils
的部分實現(xiàn)進(jìn)行分析 :
在調(diào)用supportNest()
方法的時候害幅,我們先為RadioGroup
設(shè)置了OnHierarchyChangeListener
監(jiān)聽,在監(jiān)聽中岂昭,如果子View
是RadioButton
則執(zhí)行dispatchChildViewAdded()
方法進(jìn)行對應(yīng)的添加和移除以现,最終也是調(diào)用進(jìn)行traversalSetOnCheckedChangeWidgetListener()
方法。然后調(diào)用traversalSetOnCheckedChangeWidgetListener()
约啊,這里調(diào)用一次進(jìn)行綁定是因為RadioGroup
在設(shè)置OnHierarchyChangeListener
的時候邑遏,view
已經(jīng)通過xml Inflate
加入了RadioGroup
中,在xml
設(shè)置的RadioGroup
子View
不會調(diào)用我們設(shè)置的OnHierarchyChangeListener
中的onChildViewAdded
和onChildViewAdded
方法
traversalSetOnCheckedChangeWidgetListener()
方法做的事情比較簡單恰矩,就是對傳入的view
進(jìn)行遞歸记盒,如果是View
的話,我們執(zhí)行setOnCheckedChangeWidgetListener()
方法外傅。如果是ViewGroup
(RadioGroup
不進(jìn)行處理纪吮,這樣可以防止對RadioGroup
嵌套RadioGroup
造成干擾)則為其添加或者移除OnHierarchyChangeListener
監(jiān)聽俩檬。
setOnCheckedChangeWidgetListener()
做了什么事情呢?通過上面的分析可以知道碾盟,其實RadioGroup
是通過給RadioButton
添加監(jiān)聽來實現(xiàn)單選棚辽,那我們只需要為RadioButton
都加上監(jiān)聽(這里是通過反射的方式實現(xiàn)的,因為這些方法和變量都是私有的)冰肴,并且當(dāng)RadioButton
不存在id
的時候屈藐,為其設(shè)置一個id
,上面已經(jīng)提到RadioGroup
是通過id
來判斷當(dāng)前選中哪個RadioButton
熙尉。
這樣就可以實現(xiàn)網(wǎng)上大部分自定義RadioGroup
的功能联逻,實現(xiàn)xml
布局嵌套單選的功能,但是代碼動態(tài)添加就會存在問題检痰,為什么呢包归?認(rèn)真看的小伙伴應(yīng)該已經(jīng)發(fā)現(xiàn)問題了,上面我有說到OnHierarchyChangeListener
只能監(jiān)聽到直接子View
的添加和移除,也就是說攀细,如下圖所示箫踩,情況1是可以產(chǎn)生互斥效果,情況2是無效的
//情況1:直接添加在RadioGroup上
RadioButton radioButton = new RadioButton(IntentActivity.this);
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics());
radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height, 1));
radioButton.setText("haha");
rg.addView(radioButton);
//情況2:添加在RadioGroup的子View LinearLayout上(rg.getChildAt(2))
RadioButton radioButton2 = new RadioButton(IntentActivity.this);
radioButton2.setLayoutParams(new RadioGroup.LayoutParams(0, height, 1));
radioButton2.setText("sec");
((ViewGroup) rg.getChildAt(2)).addView(radioButton2);
那要怎么辦谭贪?我們通過查看源碼可以發(fā)現(xiàn)setOnHierarchyChangeListener()
是ViewGroup
的方法境钟,所以我們可以在RadioGroup
添加子View
的時候為所有的ViewGroup
都設(shè)置上OnHierarchyChangeListener
監(jiān)聽,這樣無論嵌套多少層的子View
添加RadioButton
俭识,我們都可以為其設(shè)置監(jiān)聽慨削,這樣就可以實現(xiàn)無限嵌套互斥的功能了。 所以我們在setOnCheckedChangeWidgetListener()
方法中為子ViewGroup
設(shè)置了OnHierarchyChangeListener
套媚。這里我進(jìn)行了一些處理缚态,使用了代理的方式,即可以設(shè)置監(jiān)聽 堤瘤,也可以保證子ViewGroup
原來的OnHierarchyChangeListener
監(jiān)聽玫芦。
這樣,我們的功能就實現(xiàn)了本辐,效果如下圖所示: