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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容