一行代码无侵入实现RadioGroup无限嵌套互斥
最近有一个需求,需要实现单选的功能,类似这个
Paste_Image.png像这种结构,通过RecyclerView
,FlexBoxLayout
之类的都可以实现。然而相对比较麻烦,我想通过RadioGroup
来实现,但是通过RadioGroup
来实现的话也存在一些问题,RadioGroup
本身是继承于LinearLayout
,并不能实现以上布局。进行嵌套的话,则会失去单选的功能,这是为什么呢?通过查看源码的话可以发现,RadioGroup
设置了OnHierarchyChangeListener
监听(可以检测到直接子
View
的添加和移除)。
在监听到添加子View
的时候,(上图375行代码处可见)如果子View
是RadioButton
的话,则添加setONCheckedChangeWidgetListener()
监听,如果RadioButton
不存在id
的话,则通过View.generateViewId()
为其设置一个id
(RadioGroup
内部是通过id
来判断当前选中哪个RadioButton
),不是则不进行处理。setONCheckedChangeWidgetListener
的作用就是当RadioButton
被选中的时候进行通知RadioGroup
,收到通知后,(下图346行代码处) RadioGroup
就会进行处理,会调用setCheckedStateForView()
方法,这个方法会把上一个选中的RadioButton
取消选中状态,然后调用351行的 setCheckedId()
方法,设置当前选中 RadioGroup
的id
,并回调我们平时设置的setOnCheckedChangeListener
中的OnCheckedChangeListener
。
这样我们就明白了RadioGroup
如何实现选中的,也明白了为什么嵌套后会失去单选效果。
这个时候,我肯定会按照惯例去Google一下有没有轮子,然而发现了很多都是自定义View的,基本上都是继承LinearLayout
实现一套类似RadioGroup
的实现,这样会失去原有的RadioGroup
的特性,而且也存在一个明显的问题,就是只能够在xml
布局中实现互斥,如果在代码中进行动态添加的话,则会失去单选的效果。
于是我想有没有一个办法,可以无侵入式的实现RadioGroup
的无限嵌套,事实证明可以。
我们先来看一下使用方法:
new RadioGroupUtils(rg).supportNest();
对的,就是这么一句话就可以了。下面我们对RadioGroupUtils
的部分实现进行分析 :
在调用supportNest()
方法的时候,我们先为RadioGroup
设置了OnHierarchyChangeListener
监听,在监听中,如果子View
是RadioButton
则执行dispatchChildViewAdded()
方法进行对应的添加和移除,最终也是调用进行traversalSetOnCheckedChangeWidgetListener()
方法。然后调用traversalSetOnCheckedChangeWidgetListener()
,这里调用一次进行绑定是因为RadioGroup
在设置OnHierarchyChangeListener
的时候,view
已经通过xml Inflate
加入了RadioGroup
中,在xml
设置的RadioGroup
子View
不会调用我们设置的OnHierarchyChangeListener
中的onChildViewAdded
和onChildViewAdded
方法
traversalSetOnCheckedChangeWidgetListener()
方法做的事情比较简单,就是对传入的view
进行递归,如果是View
的话,我们执行setOnCheckedChangeWidgetListener()
方法。如果是ViewGroup
(RadioGroup
不进行处理,这样可以防止对RadioGroup
嵌套RadioGroup
造成干扰)则为其添加或者移除OnHierarchyChangeListener
监听。
setOnCheckedChangeWidgetListener()
做了什么事情呢?通过上面的分析可以知道,其实RadioGroup
是通过给RadioButton
添加监听来实现单选,那我们只需要为RadioButton
都加上监听(这里是通过反射的方式实现的,因为这些方法和变量都是私有的),并且当RadioButton
不存在id
的时候,为其设置一个id
,上面已经提到RadioGroup
是通过id
来判断当前选中哪个RadioButton
。
这样就可以实现网上大部分自定义RadioGroup
的功能,实现xml
布局嵌套单选的功能,但是代码动态添加就会存在问题,为什么呢?认真看的小伙伴应该已经发现问题了,上面我有说到OnHierarchyChangeListener
只能监听到直接子View
的添加和移除,也就是说,如下图所示,情况1是可以产生互斥效果,情况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);
那要怎么办?我们通过查看源码可以发现setOnHierarchyChangeListener()
是ViewGroup
的方法,所以我们可以在RadioGroup
添加子View
的时候为所有的ViewGroup
都设置上OnHierarchyChangeListener
监听,这样无论嵌套多少层的子View
添加RadioButton
,我们都可以为其设置监听,这样就可以实现无限嵌套互斥的功能了。 所以我们在setOnCheckedChangeWidgetListener()
方法中为子ViewGroup
设置了OnHierarchyChangeListener
。这里我进行了一些处理,使用了代理的方式,即可以设置监听 ,也可以保证子ViewGroup
原来的OnHierarchyChangeListener
监听。
这样,我们的功能就实现了,效果如下图所示:
a.gif