疫情期间闲在家中实在是无聊,游戏也提不起兴趣,一天天除了吃就是睡,哪儿也去不了,本以为自己挺宅的,没想到疫情让我从新认识了自己,原来自己也是有一颗向往自由的心啊!突然就理解了那些长期被关在家中盼望出去撒泼的狗子是一种怎样的心情了!
罢了罢了,抱着想起电脑中留有不少以前写的Demo,看看能不能捡个出来写(shui)篇博客的想法,于是就产生了这篇文章。
滑动冲突在Android中是一个比较常见的问题,尤其是在一些效果比较复杂的页面会经常遇到,今天我就ScrollView嵌套ScrollView滑动冲突问题,从问题解析、解决思路、实际代码来一步步给大家进行讲解,如果哪里讲的不好,还希望大家勇于闭嘴。
问题解析
追根溯源,滑动冲突会产生的根本原因在于滑动没有被正确的执行者执行,属于内部的滑动被外部劫持消耗掉了。
要想理解这个问题需要我们对Android中的事件分发机制有所了解,如果对此机制不了解的伙伴可以查看这篇博客—包你看懂Android中的事件分发机制
那么,我们看看ScrollView中对于滑动事件是如何做处理的。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
* 当用户手指在屏幕上处于滑动状态时,拦截这个动作。
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
/*...*/
}
看到了不,有理有据,这锅ScrollView自己背了。
解决思路
1、内部获取滑动控制权
那么我想问你一下电脑前的各位,发现了原因后你们知道怎么解决了吗?其实很简单,控制外层不要让它拦截属于内层的滑动就好了,那么怎么做呢,我发现在ScrollView的onInterceptTouchEvent()中并没有对用户手指摁下的动作进行拦截,也就是没有对MotionEvent.ACTION_DOWN进行拦截,这就意味着当用户手指摁下时,这个动作是可以传递到ScrollVIew内部中其他VIew上的(不然ScrollView中其他View的点击长摁事件怎么响应),那么我们就可以重写一个ScrollView,当它接收到MotionEvent.ACTION_DOWN这个动作时请求外层不对动作进行拦截。这点很重要,因为这时内部的ScrollVIew就掌握了主动权,可以处理用户手指在屏幕上的一系列操作,
2、内外层滑动关联
那么,到这里就解决完了吗?不不不,这只是开头,如果此时你跟着我上面的思路敲完了代码,你会发现内外两个ScrollVIew的滑动事件是独立的,虽然内部的ScrollView可以滑动,但是当它滑动到顶部或底部时并没有跟外部的ScrollView进行联动,这样用户的滑动体验会很差,怎么解决呢?发动你机智的小脑瓜想一想,对咯,那就是在合适的时机将外层的拦截权还给它。那什么是合适的时机呢?无非就是内部ScrollView滑动到顶或者底的时候。
OK,问题都清楚了,方案也提供了,现在你就可以试着写写代码尝试解决冲突了。
实际代码
这里我把我写的代码放出来,供大家参考。
public class MyScrollView extends ScrollView {
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
maxY = getChildAt(0).getMeasuredHeight() - getMeasuredHeight();
}
//在Y轴上可以滑动的最大距离 = 总长度 - 当前展示区域长度
private int maxY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (getScrollY() > 0 && getScrollY() < maxY)
getParent().requestDisallowInterceptTouchEvent(true);
else
getParent().requestDisallowInterceptTouchEvent(false);
/*if (getScrollY()==0)
Log.i("kkk","滑到头了");
if (getChildAt(0).getMeasuredHeight() == getScrollY() + getHeight())
Log.i("kkk","滑到底了");*/
break;
case MotionEvent.ACTION_UP:
getParent().getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
}
布局结构
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<wowo.kjt.scrolldemo.MyScrollView
android:layout_width="match_parent"
android:layout_height="300dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aaa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aaa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aaa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aaa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aaa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aaa"
android:text="对对对" />
</LinearLayout>
</wowo.kjt.scrolldemo.MyScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#faa"
android:text="对对对" />
</LinearLayout>
</ScrollView>
重写后的ScrollView嵌套滑动效果
最后
在我最开始接触Android开发的时候,对这种事件冲突的问题是一点办法都没有,因为不知道怎么排查问题,也不知道如何下手,不过随着对Android知识的不断加深,反过头来在看这些问题也不过如此,人生又何尝不是这样呢,不管过去还是未来,总是有着我们不懂的问题在等着我们去解决,而我们则在这个解决的过程中不断的进步和成长,成就更好的自己。对于今年突发的新冠状病毒,已经不仅仅是我们个人要面临的问题,更是我们所有中国人需要共同面对去解决的问题,真心希望这场疫情可以快点过去,也祝愿那些在抗疫一线的工作人员能够保重身体,你们是最美最帅的!中国加油!!!