在我们平时项目开发的过程中,是不是会遇到滑动View之间的相互嵌套,比如外部的Scrollview或SwipeRefreshLayout嵌套内部的Viewpager或横向Recyclerview,如外部Viewpager嵌套内部Viewpager。这样往往就会造成滑动的冲突导致不流畅甚至根本滑不动。事件分发机制见我的另一篇Android事件分发机制,用事实说话。
滑动冲突产生的两个根本原因:
- 外部滑动方向与内部方向不一致
- 外部滑动方向与内部方向一致
扎心了,一不一致都可能造成滑动冲突。第一种如ScrollView 嵌套ViewPager,第二种如ViewPager嵌套ViewPager。
解决方案:
外部拦截法:
从父View着手,重写onInterceptTouchEvent方法,在父View需要拦截的时候拦截事件,不需要则不拦截返回false。伪代码如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if(父View拦截条件){
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
下面我们来看一个真实的案例:
SwipeRefreshLayout嵌套ViewPager,需要流畅横向滑动Viewpager,这种操作下通常会顺带牵连到SwipeRefreshLayout致其拉动,如示例:
giphy.gif用外部拦截法自定义一个MySwipeRefreshLayout,在开始滑动的条件下,判断手势的横纵方向的距离大小,判断用户到底是要横向滑动还是纵向滑动。纵向滑动意为拉动刷新,就拦截事件,横向滑动就意为滑动viewpager,就不拦截事件。解决效果:
giphy.gifMySwipeRefreshLayout代码如下:
import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
public class MySwipeRefreshLayout extends SwipeRefreshLayout{
private float startX;
private float startY;
private float mTouchSlop;
public MySwipeRefreshLayout(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float distanceX = Math.abs(ev.getX() - startX);
float distanceY = Math.abs(ev.getY() - startY);
if(distanceX > mTouchSlop && distanceX > distanceY){ //判断为横向滑动
return false;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
}
getScaledTouchSlop()表示能触发滚动的最小距离,如果小于这个距离就不触发移动控件。
/**
* @return Distance in pixels a touch can wander before we think the user is scrolling
*/
public int getScaledTouchSlop() {
return mTouchSlop;
}
内部拦截法:(getParent().requestDisallowInterceptTouchEvent(true),请求父容器不拦截事件)
从子View入手,重写子元素的dispatchTouchEvent方法,父View先不要拦截任何事件,所有的事件传递给子View,如果子View需要此事件就消费掉,不需要此事件的话就通过requestDisallowInterceptTouchEvent(true)方法交给父View处理。伪代码如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(事件交给父View条件){
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onInterceptTouchEvent(ev);
}
父View需要重写:默认不拦截子View事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
案例:Viewpager嵌套多个ViewPager:需求是默认滑动内部Viewpager,当内部Viewpager滑动到首页或者末页时,就滑动外部Viewpager切换页面。解决效果:
giphy.gif自定义子Viewpager:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class InerViewPager extends ViewPager{
private int itemCount;
public InerViewPager(Context context) {
super(context);
}
public InerViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
itemCount = getAdapter().getCount();
if(getCurrentItem() == 0 || getCurrentItem() == itemCount - 1){
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onInterceptTouchEvent(ev);
}
}
这里举的是基本栗子,那如果产品经理的想象力丰富,难度系数5.0以上呢?要是开发中遇到较复杂的滑动冲突,我只能说来一个杀一个,来两个杀一双!
网友评论