您的位置:首页 > 运维架构

解决ViewPager与父ViewGrop的事件冲突

2017-04-02 21:02 393 查看
在做公司项目的时候,遇到这样一个需求:activity最下层是一个地图控件,可以从下方划出一个layout占据下半个屏幕,也可以下滑收起状态(占1/5屏幕),该layout用于文字显示一条路线,可以左右滑动,切换不同路线(类似画廊效果),展开和收起状态都可以左右滑动,切换后地图上显示对应的路线。

我的思路是自定义一个layout,实现滑动展开和收起,这个简单,不一会就敲了一个demo:

public class SlideLayout extends RelativeLayout implements GestureDetector.OnGestureListener {

private Scroller scroller;
private GestureDetector detector;
private int height;//此layout的高度
private int up;//展开的scrollY值
private int down;//收起的scrollY值
private int middle;//中间scrollY值

public SlideLayout(Context context) {
this(context, null);
}

public SlideLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context) {
scroller = new Scroller(context);
detector = new GestureDetector(context, this);
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
height = getHeight();
up = 0;
down = -height * 2 / 3;
middle = (up + down) / 2;
Log.i("tag", "init");
}
});
}

public int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
//事件交给GestureDetector处理
return detector.onTouchEvent(event);
}

@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
invalidate();
}
}

@Override
public boolean onDown(MotionEvent e) {
return e.getY() >= Math.abs(getScrollY());
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//        Log.i("tag", "onScroll:" + distanceY);
if (getScrollY() > up || getScrollY() < down) {
return false;
}
scrollBy(0, Math.round(distanceY * 0.5f));
return true;
}

@Override
public void onLongPress(MotionEvent e) {

}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.i("tag", "onFling:" + velocityY);
//确定滑动方向
int target;
if (velocityY > 500) {
target = down;
} else if (velocityY < -500) {
target = up;
} else {
if (e2.getY() > Math.abs(middle)) {
target = down;
} else {
target = up;
}
}
//开始滑动
smoothScrollTo(target);
return true;
}

private void smoothScrollTo(int target) {
scroller.startScroll(0, getScrollY(), 0, target - getScrollY());
invalidate();
}
}


测试了下,非常不错,已经实现了滑动展开和收起的效果,接下来只要在里面放一个ViewPager就可以了~

马上又写了一个放进去,结果发现layout的滑动效果没了,只有ViewPager响应了事件,想到可能是ViewPager抢夺了事件,于是马上自定义一个ViewPager,只消耗水平方向的时间,仍然没有效果。

奇怪了,不应该啊!

断点看了一下VIewPager的onTouchEvent方法,发现如果是竖直方向的事件并没有消耗,确实返回false!又看下SlideLayout的onTouchEvent方法没有执行,说明没有接收到事件。

尼玛!我的事件被谁吃了!按理说子View的onTouchEvent返回false,应该会把事件还给父View,执行父View的onTouchEvent才对啊!

静下几分钟后,既然ViewPager没有把事件还给SlideLayout,那我就在SlideLayout中拦截合适的事件,于是有了下面的改动:

public class SlideLayout extends RelativeLayout implements GestureDetector.OnGestureListener {

private Scroller scroller;
private GestureDetector detector;
private int height;//此layout的高度
private int up;//展开的scrollY值
private int down;//收起的scrollY值
private int middle;//中间scrollY值
private int slideHead;//可以触发此layout滑动的高度

public SlideLayout(Context context) {
this(context, null);
}

public SlideLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context) {
scroller = new Scroller(context);
detector = new GestureDetector(context, this);
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
height = getHeight();
up = 0;
down = -height * 2 / 3;
middle = (up + down) / 2;
slideHead = dp2px(getContext(), 100);
Log.i("tag", "init");
}
});
}

public int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
int abs = Math.abs(getScrollY());
//如果down的位置在在设定范围内则直接拦截事件
if (ev.getY() < abs + slideHead && ev.getY() > abs) {
Log.i("tag", "intercept");
return true;
}
}
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
//事件交给GestureDetector处理
return detector.onTouchEvent(event);
}

@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
invalidate();
}
}

@Override
public boolean onDown(MotionEvent e) {
return e.getY() >= Math.abs(getScrollY());
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//        Log.i("tag", "onScroll:" + distanceY);
if (getScrollY() > up || getScrollY() < down) {
return false;
}
scrollBy(0, Math.round(distanceY * 0.5f));
return true;
}

@Override
public void onLongPress(MotionEvent e) {

}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.i("tag", "onFling:" + velocityY);
//确定滑动方向
int target;
if (velocityY > 500) {
target = down;
} else if (velocityY < -500) {
target = up;
} else {
if (e2.getY() > Math.abs(middle)) {
target = down;
} else {
target = up;
}
}
//开始滑动
smoothScrollTo(target);
return true;
}

private void smoothScrollTo(int target) {
scroller.startScroll(0, getScrollY(), 0, target - getScrollY());
invalidate();
}
}


我设定一个范围,只要在范围的事件就会触发layout的滑动,而范围外的事件则分发给ViewPager去消耗。虽然实现业务的需求,但是我始终没有明白为什么ViewPager消耗了它不需要的事件,不知道哪位大神可以告诉我原因?

ps:一个小问题onFling方法是必然执行的吗?(onDown返回了true)我在测试的时候发现,在一个测试机中onFling是必然触发的(无论是多慢地离开手指),而在另一个测试机上,慢慢滑动后手指离开屏幕不会触发onFling,所以感觉很奇怪!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息