美文网首页
滑动回弹与内层listview的滑动冲突

滑动回弹与内层listview的滑动冲突

作者: _蘇芳_ | 来源:发表于2017-01-19 17:52 被阅读104次

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,全部搞定,收工~

相关文章

网友评论

      本文标题:滑动回弹与内层listview的滑动冲突

      本文链接:https://www.haomeiwen.com/subject/omiibttx.html