问题
在ScrollView中嵌套EditText是非常常见的,例如下面的页面:

投诉反馈具体内容这块,要求里面的EditText可以上下滑动,但是它嵌套在ScrollView里面,无法滑动。
原因
外层ScrollView将滑动事件消费了,导致滑动事件没有向下传递,EditText根本没有接收到该事件。
这里就要说一下事件分发机制了:
一般情况下事件分发会遵循activity / fragment-->ViewGroup-->View,其中涉及事件分发的核心方法如下:
- dispatchTouchEvent()
- onTouchEvent()
- onInterceptTouchEvent()
但并非所有组件中都含有这三个方法,它们的存在情况如下:
| 组件 | dispatchTouchEvent() | onTouchEvent() | onInterceptTouchEvent() |
|---|---|---|---|
| activity | 存在 | 存在 | 不存在 |
| ViewGroup | 存在 | 存在 | 存在 |
| View | 存在 | 存在 | 不存在 |
它们的作用如下:
| 方法 | 作用 | 调用时刻 |
|---|---|---|
| dispatchTouchEvent() | 分发(传递)点击事件 | 当点击事件能够被传递给当前View时调用 |
| onTouchEvent() | 处理点击事件 | 在dispatchTouchEvent()内部调用 |
| onInterceptTouchEvent() | 判断是否拦截某个事件 | 在dispatchTouchEvent()内部调用 |
它们的返回结果如下:

看到这里,相信大家已经想到好几种解决方法了吧。下面给出我的解决方案:
解决
新建类SolveEditTextScrollClash 实现 View.OnTouchListener
重写onTouch()如下:
@Override
public boolean onTouch(View view, MotionEvent event) {
//触摸的是EditText而且当前EditText能够滚动则将事件交给EditText处理。否则将事件交由其父类处理
if ((view.getId() == editText.getId() && canVerticalScroll(editText))) {
view.getParent().requestDisallowInterceptTouchEvent(true);
if (event.getAction() == MotionEvent.ACTION_UP) {
view.getParent().requestDisallowInterceptTouchEvent(false);
}
}
return false;
}
其中canVerticalScroll()如下:
/**
* EditText竖直方向能否够滚动
* @param editText 须要推断的EditText
* @return true:能够滚动 false:不能够滚动
*/
private boolean canVerticalScroll(EditText editText) {
//滚动的距离
int scrollY = editText.getScrollY();
//控件内容的总高度
int scrollRange = editText.getLayout().getHeight();
//控件实际显示的高度
int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() -editText.getCompoundPaddingBottom();
//控件内容总高度与实际显示高度的差值
int scrollDifference = scrollRange - scrollExtent;
if(scrollDifference == 0) {
return false;
}
return (scrollY > 0) || (scrollY < scrollDifference - 1);
}
完整的类如下:
public class SolveEditTextScrollClash implements View.OnTouchListener {
private EditText editText;
public SolveEditTextScrollClash(EditText editText) {
this.editText = editText;
}
@Override
public boolean onTouch(View view, MotionEvent event) {
//触摸的是EditText而且当前EditText能够滚动则将事件交给EditText处理。否则将事件交由其父类处理
if ((view.getId() == editText.getId() && canVerticalScroll(editText))) {
view.getParent().requestDisallowInterceptTouchEvent(true);
if (event.getAction() == MotionEvent.ACTION_UP) {
view.getParent().requestDisallowInterceptTouchEvent(false);
}
}
return false;
}
/**
* EditText竖直方向能否够滚动
* @param editText 须要推断的EditText
* @return true:能够滚动 false:不能够滚动
*/
private boolean canVerticalScroll(EditText editText) {
//滚动的距离
int scrollY = editText.getScrollY();
//控件内容的总高度
int scrollRange = editText.getLayout().getHeight();
//控件实际显示的高度
int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() -editText.getCompoundPaddingBottom();
//控件内容总高度与实际显示高度的差值
int scrollDifference = scrollRange - scrollExtent;
if(scrollDifference == 0) {
return false;
}
return (scrollY > 0) || (scrollY < scrollDifference - 1);
}
}
用法如下:
etContent.setOnTouchListener(new SolveEditTextScrollClash(etContent));
思考
ScrollView内嵌套其他能滑动的控件时都会遇到这样的问题,其解决方法都与上面类似,重写滑动冲突控件的onTouch()方法。
结语
能力有限,文章中可能有错误或者不严谨的地方,欢迎批评指正,不喜勿喷。