作者在编写一个文章编辑页面,效果图如下:
在完成Beta版本之后,被要求增加一个小需求,要求在切换页面的时候,必须对原来页面的内容进行判断,如果页面没有内容,那么直接切换过去;假如原页面含有内容,需要弹出一个对话框,让用户二次确认。效果如下:
由于UI上有对两个Title下划线指示器长短的要求,所以采用了ViewPager+RadioGroup来实现这个页面。
关于如何自定义实现指示器长度,作者采用的是RadioGroup,通过修改背景来实现。
背景实现可以参考另一篇文章:
Shape为View定制下划线
也通过其它方式去修改Title下划线指示器的文章:
Android 自定义ViewPager下划线指示器Indicator长度
本篇文章的重点不是在此,便不再细说。
为了能切换的时候,弹出确认框。作者给每一个RadioButton增加点击监听:
opinionRadioButton.setOnClickListener {
if(页面是否有输入内容){
弹出切换确认对话框
}else{
直接切换过去
}
}
运行之后,发现页面毫无波澜。代码没有执行OnClickListener里面的onClick方法。事件被消费掉了,没有下发到RadioButton的OnClickListener。
所以,问题的核心就是截取点击事件,不能让RadioButton或者RadioGroup消费掉,我们要自己决定是否消费掉这个事件。
了解事件分发的原理,我们都知道,事件分发都是从上层交给下层进行的。也就是容器类ViewGroup首先会将事件分发给下面的子View,如果子View不处理,ViewGroup才去处理。
而截取事件,就是要打断事件下发的流程。按照业务逻辑,那就是页面内容决定了是否拦截。
这样看起来,思路就简单多了。在RadioGroup判断是否拦截事件,如果需要拦截,那么自身消费掉。
需要自定义一个RadioGroup,重写onInterceptTouchEvent方法,控制RadioGroup是否消费事件。
public class InterceptRadioGroup extends RadioGroup {
private InterceptListener externalListener;
public InterceptRadioGroup(Context context) {
super(context);
}
public InterceptRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (externalListener != null) {
if (externalListener.onExternalIntercept(ev)) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
return super.onInterceptTouchEvent(ev);
}
public View findViewByXY(float x, float y) {
View v = null;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
Rect rect = new Rect(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
if (!rect.contains((int) x, (int) y)) {
continue;
}
v = child;
break;
}
return v;
}
public void setExternalListener(InterceptListener externalListener) {
this.externalListener = externalListener;
}
public interface InterceptListener {
boolean onExternalIntercept(MotionEvent ev);
}
}
定义一个InterceptListener接口,里面只有一个回调方法onExternalIntercept,返回true则表示消费掉这个事件。onInterceptTouchEvent的核心就是调用这个方法来决定是否继续下发事件,以此打断RadiaoGroup的切换。
具体实现:
typeRadioGroup.setExternalListener(object : InterceptRadioGroup.InterceptListener {
override fun onExternalIntercept(ev: MotionEvent?): Boolean {
//如果页面没有内容不拦截,isNOtInterceptEvent()是true,onExternalIntercept返回false
if (isNOtInterceptEvent()) {
return false;
}
var result = false
ev?.let {
//判断点击到哪个Button
val view = typeRadioGroup.findViewByXY(ev.x, ev.y)
if (view is RadioButton) {
val button = view as RadioButton
//过滤重复点击
if (button.isChecked) {
result = false
} else {
//弹出确认框,onExternalIntercept返回true,不让事件继续下发
val switchDialog = BaseRemindDialog.Build(this@EditArticleActivity)
.setTitle(getString(R.string.dialog_title_tip))
.setContent(getString(R.string.text_ensure_switch_edit))
.setLeftBtn(getString(R.string.text_cancel))
.setRightBtn(getString(R.string.text_confirm))
.setRightColor(ContextCompat.getColor(this@EditArticleActivity, R.color.text_color_black))
.setListent(object : BaseRemindDialog.OnClickListent {
override fun onLeftClick() {
}
override fun onRightClick() {
//事件已经被消费了,需要手动去修改RadioButton的选中状态
typeRadioGroup.check(view.id)
}
}).build()
switchDialog.show()
}
result = true
} else {
result = false
}
}
return result
}
})