title: 滑动回弹与内层listview的滑动冲突
date: 2016-12-06 10:33:27
tags: problems
- 需求:
1、随着下拉,view发生位移,松开回弹到原来的位置
2、内部的listview可以正常的上下滑动
3、listview滑到顶部的时候,继续下拉,则是拉动整个外部view,并且松开回弹
这3个需求就会造成事件冲突,那么处理方式就是:listview不是初始状态就是listview自己处理事件,listview还原到了初始状态,外部view处理下拉回弹事件。
需求一个一个的实现,首先第一个下拉回弹
因为里面还要套一个listview,所以我们自定义一个view继承自viewGroup,这里选择的是LinearLayout
package com.aidebar.demo;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
/**
* @author xzj
* @date 2016/12/6 10:54.
*/
public class MyView extends LinearLayout {
private int startY;
private int moveY;
private int diffY;
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY(); //getY()获取的是按下去的位置在view中的纵坐标
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = y;
break;
case MotionEvent.ACTION_MOVE:
moveY = y;
Log.d("myview", "moveY="+moveY+"|startY="+startY);
//当往下滑动的时候
if ((moveY - startY) > 0) {
//获取到位移距离,并改变布局参数让view动起来
layout(getLeft(), getTop() + (moveY - startY), getRight(), getBottom() + (moveY - startY));
//diffY是用来记录总共的位移数据的,用于在ACTION_UP中还原
//每次走进move都位移了一点点,就重新布局一次,把每次位移的这一点点累加起来
diffY += (moveY - startY);
}
break;
case MotionEvent.ACTION_UP:
// 在ACTION_UP中就不能用moveY-startY了
// 因为每次走到ACTION_MOVE的时候moveY获取的是触摸点离view上边界的距离,在ACTION_MOVE里重新布局后moveY离上边界肯定是固定的,startY在不放手的情况下也是固定的
// 所以如果用moveY-startY的话会是0,就无法回弹了
layout(getLeft(), getTop() - diffY, getRight(), getBottom() - diffY);
diffY = 0;
break;
}
return true;
}
}
OK,实现第二条需求,让内部listview可以滑动,要让子view可以接受到MotionEvent,首先我们自己就不能拦截,那么重写onInterceptTouchEvent()
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean isIntercept=false;
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = y;
break;
case MotionEvent.ACTION_MOVE:
moveY = y;
//当往下滑动的时候才拦截,
if ((moveY - startY) > 0) {
isIntercept = true;
}else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return isIntercept;
}
好,拦截方法写完了,但这样的话,所有向下滑动都被我们拦截了,listview就不能向下滑了。
但这是listview的事情,应该由listview来做判断,什么时候拦截什么时候不拦截。
自定义一个MyListView,继承自ListView,并重写onTouchEvent()
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//down被拦截了,后续所有事件就都收不到了
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (getScrollY() == 0) {
//只有当listview还原了,才将事件交给外层,由外层拦截事件
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return super.onTouchEvent(ev);
}
这么写会发现无效,因为listview不想scrollview,它没有重写getScrollY()方法,直接调用的是父类view的方法,返回值永远是0. 没写也没关系,我们自己写
//listview没有重写getScrollY方法,我们只能自己写,可是这方法是final的。。所以不要吐槽名字
public int getScrollY1() {
View v = getChildAt(0);
if (v == null) {
return 0;
}
int firstVisiblePosition = getFirstVisiblePosition();
//top的值肯定是<=0的,因为第一个view完全展示的时候top为0,滑上去了就是负数
int top = v.getTop();
return -top + firstVisiblePosition * v.getHeight() ;
}
将上面的getScrollY()
替换成getScrollY1()
即可。
OK,listview可以正常滑动了,第二条需求完成
大功告成?太年轻了。。
你会发现可以下拉回弹,listview可以上下滑动并且下拉回弹,但是!
你先把listview往上滑一下,松手,然后再下拉试试
会发现在临界状态下,外部的view突然往下移动了一大截。
为什么不松手的情况下,listview可以上下滑动,滑到顶了外部view可以正常下拉并回弹,而先滑动一次listview就不行了呢?
因为我们在外部view的onInterceptTouchEvent()
里获取到了startY,所以当listview复原的时候的moveY和这个startY是相等的,外部view就可以正常的下拉回弹。
而先滑动一次listview后,再次点击滑动,获取到的是一个新的startY,而此时你要把listview复原的moveY是大于startY的,所以listview滑到顶的时候再下拉,布局会突然下降一截。
知道原因就好解决了,在listview处理滑动事件的时候,复原的时候,将moveY的值给外部view的startY赋值就行了呗!
怎么传值,请看RxBus工具类,这是用rxjava写的一个取代EventBus的工具。
在MyView中初始化的时候注册一下
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
RxBus.getInstance().toObservable(Integer.class,"startY")
.subscribe(new RxBusSubscriber<Integer>() {
@Override
public void receive(Integer data) {
startY = data;
}
});
}
在MyListView中加一句
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (getScrollY1() == 0) {
//这里加一句,将此时的坐标发给MyView
RxBus.getInstance().send((int)ev.getY(),"startY");
getParent().requestDisallowInterceptTouchEvent(false);
}else {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
OK,全部搞定,收工~