很多时候,需要在开发中需要实现类似RadioGroup和RadioButton结合使用实现的单选效果,但是RadioGroup和RadioButton结合的实现效果不能完全定制化,不能适应所有的APP开发需求,这里简单的剖析下RadioGroup的实现原理,来完成自定义RadioGroup显示单选。
在之前写这种实现的时候,因为没有阅读过源码,实现的比较挫,简单的用一个List存放所有的单元Item,然后在点击事件中,遍历所有的View,挨个设置点击事件,虽然也能实现点击效果但是效率低的不行,而且感觉写起来就是一种垃圾代码,写过几次之后,决定研究下原生的代码写法,脱离低级,向优秀进发。
下面看一下RadioGroup的源码吧:
首先RadioGroup是继承的LinearLayout,这就很多时候限制了我们一些开发的实现
public class RadioGroup extends LinearLayout
如果自定义的话,这东西就灵活多了
实现单选的关键部分是
private int mCheckedId = -1;
这个是用来存储单选选中的view的id值的,
RadioGroup重写了addView方法
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof RadioButton) {
final RadioButton button = (RadioButton) child;
if (button.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.getId());
}
}
super.addView(child, index, params);
}
可以看到首先RadioGroup会先判断子view是否是RadioButton,如果是,判断是否默认已经被选择
private void setCheckedStateForView(int viewId, boolean checked) {
View checkedView = findViewById(viewId);
if (checkedView != null && checkedView instanceof RadioButton) {
((RadioButton) checkedView).setChecked(checked);
}
}
setCheckedStateForView(int viewId, boolean checked)方法是用来实现点击效果的,这个地方很好理解。
那么addView剩下的只剩一个setCheckedId(id)这个方法了
private void setCheckedId(@IdRes int id) {
mCheckedId = id;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
}
这里就是实现之前说的,将单选的view的id赋值给mCheckedId ,并且,额外的,绑定了监听器
在addView之后,RadioGroup重新了这个方法
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// checks the appropriate radio button as requested in the XML file
if (mCheckedId != -1) {
mProtectFromCheckedChange = true;
setCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
setCheckedId(mCheckedId);
}
}
这个方法是确保在整个addView结束后,最终确定显示被点击的Item;
单选电机的实现原理主要是以下这个方法:
public void check(@IdRes int id) {
// don't even bother
if (id != -1 && (id == mCheckedId)) {
return;
}
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
if (id != -1) {
setCheckedStateForView(id, true);
}
setCheckedId(id);
}
这里的意思就是,首先确定点击的是原来被选中的项,不作处理,如果是新的项,那么先把原来的项的点击显示变成false,之后,把当前电机的项的显示变为显示状态,之后,将当前点击的id赋值给mCheckedId;
那么最后总结下逻辑:
首先,初始化mCheckedId=-1,然后addView中处理默认的点击项,点击项的id传给mCheckedId,当每次点击新的项时,根据mCheckedId,将原来的点击项置为未点击,然后根据新的项的id,把新的项置为点击,最后,新的项的id赋值给mCheckedId,来维护单选。