Android - VerticalViewPager 嵌套 RecyclerView 滑动处理

在进这家公司之前产品提过一个需求需要实现竖直方向跟水平方向的滑动翻页功能,但竖直方向又限定只能上拉翻到下一页。其实这个功能之前的哥们已经实现了,但考虑到他的实现方式不够优雅最重要的是不利于后期版本迭代,所以重新实现了这个功能。
  之前的哥们是通过 RecyclerView 嵌套 ViewPager 的方式处理,但 RecyclerView 中的 item 从第二个开始显示空白,具体原因不得而知,估计跟复用有关,有知道的朋友可以提醒。
  我这里用的是 VerticalViewPager + RecyclerView 嵌套的方式,更便于更新重新绘制UI。因为外部的 ViewPager 跟 RecyclerView 都是充满屏幕实现翻页效果的。代码不多,关键是要理解思想,并对事件传递有清晰的判断并进行拆分。

好了,下面进入正题,让我们来借助 gif 图来捋清楚需求 竖直方向从下往上滑动翻页 + 水平翻页

NestViewPager.gif

实现如下:

禁用 VerticalViewPager 从上往下翻到上一页事件

1)其中竖直方向的 ViewPager 可用 VerticalViewPager ,从下往上翻页即使 VerticalViewPager 从上往下滑动的手势失失效,可以通过重写 dispatchTouchEvent 方法控制 ,如下:

    private int xDispatchLast;
    private int yDispatchLast;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "xLastDown:" + xInterceptLast);
                Log.d(TAG, "yLastDown:" + yInterceptLast);

                xDispatchLast = (int) ev.getX();
                yDispatchLast = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final int curX = (int) ev.getX();
                final int curY = (int) ev.getY();
                Log.d(TAG, "curXMove:" + curX);
                Log.d(TAG, "curYMove:" + curY);
                int xDiff = curX - xDispatchLast;
                int yDiff = curY - yDispatchLast;
                int xAbsDiff = Math.abs(xDiff);
                int yAbsDiff = Math.abs(yDiff);
                Log.d(TAG, "xDiffMove:" + xDiff);
                Log.d(TAG, "yDiffMove:" + yDiff);
                if (yAbsDiff > xAbsDiff && yDiff > 0) {//竖直方向手指从上往下滑动,即 ViewPager 向前一页滑动时,不进行事件处理即不可向下滑动
                    Log.d(TAG, "dispatchTouchEvent");
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                xDispatchLast = (int) ev.getX();
                yDispatchLast = (int) ev.getY();
                Log.d(TAG, "xLastUp:" + xDispatchLast);
                Log.d(TAG, "yLastUp:" + yDispatchLast);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

2) VerticalViewPager+ RecyclerView 会产生事件冲突,此时只需将水平方向滑动的事件交给 RecyclerView即 RecyclerView 内部在 dispatchTouchEvent() 方法内通过 getParent().requestDisallowInterceptTouchEvent(true) 控制父控件拦截父控件的事件交给自己处理;竖直方向滑动的事件交给 VerticalViewPager,即在其 onInterceptTouchEvent() 方法直接 return true 交给自己处理。

VerticalViewPager 拦截竖直方向事件给子 View,自己处理:
    private int xInterceptLast;
    private int yInterceptLast;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "xLastDown:" + xInterceptLast);
                Log.d(TAG, "yLastDown:" + yInterceptLast);

                xInterceptLast = (int) ev.getX();
                yInterceptLast = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final int curX = (int) ev.getX();
                final int curY = (int) ev.getY();
                Log.d(TAG, "curXMove:" + curX);
                Log.d(TAG, "curYMove:" + curY);
                int xDiff = curX - xInterceptLast;
                int yDiff = curY - yInterceptLast;
                int xAbsDiff = Math.abs(xDiff);
                int yAbsDiff = Math.abs(yDiff);
                Log.d(TAG, "xDiffMove:" + xDiff);
                Log.d(TAG, "yDiffMove:" + yDiff);
                if (yAbsDiff > xAbsDiff && yDiff <= 0) {//竖直方向手指从下往上滑动,即 ViewPager 向下一页滑动时,事件由 ViewPager 自身处理
                    Log.d(TAG, "onInterceptTouchEvent");
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                xInterceptLast = (int) ev.getX();
                yInterceptLast = (int) ev.getY();
                Log.d(TAG, "xLastUp:" + xInterceptLast);
                Log.d(TAG, "yLastUp:" + yInterceptLast);
                break;
        }
        boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
        swapXY(ev); // return touch coordinates to original reference frame for any child views
        return intercepted;
    }
RecyclerView 使父控件水平方向的事件不处理交由子控件处理

3)这个代码我没有选择放在 RecyclerView 中处理,而放在其包含它的根布局中,放哪个地方可根据需求来定,如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "xLastDown:" + xLast);
                Log.d(TAG, "yLastDown:" + yLast);

                xLast = (int) ev.getX();
                yLast = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final int curX = (int) ev.getX();
                final int curY = (int) ev.getY();
                Log.d(TAG, "curXMove:" + curX);
                Log.d(TAG, "curYMove:" + curY);
                int xDiff = curX - xLast;
                int yDiff = curY - yLast;
                int xAbsDiff = Math.abs(xDiff);
                int yAbsDiff = Math.abs(yDiff);
                Log.d(TAG, "xDiffMove:" + xDiff);
                Log.d(TAG, "yDiffMove:" + yDiff);
                if (yAbsDiff < xAbsDiff || (yAbsDiff > xAbsDiff && yDiff > 0)) {//水平滑动时拦截父控件的事件
                    Log.d(TAG, "requestDisallowInterceptTouchEvent");
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);
                xLast = (int) ev.getX();
                yLast = (int) ev.getY();
                Log.d(TAG, "xLastUp:" + xLast);
                Log.d(TAG, "yLastUp:" + yLast);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

以上就能实现所想要的效果,看起来很简单吧。最后对事件分析做个总结:
  1.内部 RecyclerView 中在dispatchTouchEvent() 获取水平滑动事件。
  2.外部 VerticalViewPager 中在 onInterceptTouchEvent() 获取竖直方向滑动事件。
  3.外部 VerticalViewPager 中在 dispatchTouchEvent() 不处理下拉翻至上一页手势事件。

解决事件冲突的关键在于将事件分发进行分析,分析清楚在什么时候哪个控件处理什么手势为首要前提。当然也必须对事件分发有深入的了解。


VerticalViewPager 源代码来源于 stackoverflow
代码已上传至github: NestViewPager

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

推荐阅读更多精彩内容