Android 仿京东,淘宝RecyclerView嵌套ViewPager嵌套RecyclerView商品展示

最近看到京东,淘宝都有RecyclerView嵌套ViewPager嵌套RecyclerView商品展示的效果,效果挺好,废话不多说先看效果图:

GIF.gif

技能点:
1.Android事件分发机制等

需求点:
1.列表嵌套,内层的列表可以左右切换
2.ViewPager可以点击和滑动切换

最近在淘宝京东看到类似的效果,有时间就写了一下,效果实现了,但是感觉解决问题的思路和代码有很多瑕疵,写出来抛砖引玉,希望大佬们不吝赐教,写的不好不喜勿喷!

下面进入正题,先看下布局结构:
screen.png

就是标题所说的布局结构 RecyclerView+ViewPager+RecyclerView`

很多同学看到这里肯定想到要处理滑动冲突,没错,我们简单分析一下好撸代码(虽然是撸好的代码)
  1. 横向滑动
  • 横向滑动很简单,RecyclerView不需要处理,ViewPager处理

  1. 纵向滑动
  • 纵向滑动就稍微复杂点,本文的解决滑动冲突主要就就是解决外层RecyclerView以及内层RecyclerView的滑动冲突,仔细看下交互效果,不难发现我们需要用Tab是否吸顶作为判断的节点来将滑动事件交给外层或内层RecyclerView处理. 即: 1.Tab未吸顶时外层RecyclerView处理滑动事件,2.Tab吸顶时内层RecyclerView处理滑动事件. 这里解释一下,原来的方案是吸顶,后来我想了一下如果这个ViewPager下面没有跟多其它的样式的话,可以不用吸顶的(不能再有了,交互处理也太麻烦,有的话排版应该也不好看),\color{red}{所以下面的吸顶都是假的,只是看起来是吸顶效果}

大概就是这样,思路很清晰,这里先提几个接下来遇到的问题:

  • RecyclerView嵌套ViewPager时ViewPager的高度为0
  • 滑动冲突
  • 操作步骤:滑动到Tab吸顶->滑动内层RecyclerView至中间->切换一个Tab(内层RecyclerView的状态已经滑动到顶部,就是初始状态)->这时候将Tab滑动到非吸顶->切换到最初内层RecyClerView滑动到中间的Tab,这时候展示的就是Tab未吸顶,内层RecyclerView不在顶部的尴尬局面.说了这么多应该需要一张gif解释一下上图:
    GIF1.gif
对于上图所提到的情况,这个时候用户手指纵向滑动红色区域,滑动事件交给谁都不合适

.那先说下淘宝和京东采取的方式:

  1. 淘宝和京东部分页面切换ViewPager时候重新拉取数据(可能没有重新拉数据,只是notify了一下)将RecyclerView直接展示到初始状态
  2. 京东的部分界面(京东->我的->下拉->为你推荐)处理方式为:当Tab为非吸顶状态时候切换ViewPager,外层RecyclerView滑动到Tab吸顶
  3. demo因为用的是假数据,所以没做处理,但是代码中有在tab非吸顶状态时候,外层RecyclerView优先处理滑动事件的代码
个人感觉第一种处理方式比较好一点,demo的代码如下(需要请自行修改,PagerFragment.java)
                        if(! ((MainActivity)getActivity()).isStick){
                            ((MainActivity)getActivity()).adjustScroll(true);
                            return false;
                        }

下面说下实现方式,以及问题的解决(布局等细节就不贴出来了,详情见demo):

  1. 外部的RecyclerView为自定义的View继承自RecyclerView重写onInterceptTouchEvent方法
    处理滑动事件:

   private float downX ;    //按下时 的X坐标
    private float downY ;    //按下时 的Y坐标
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        float x= e.getX();
        float y = e.getY();
        switch (e.getAction()){
            case MotionEvent.ACTION_DOWN:
                //将按下时的坐标存储
                downX = x;
                downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //获取到距离差
                float dx= x-downX;
                float dy = y-downY;
                //通过距离差判断方向
                int orientation = getOrientation(dx, dy);
                switch (orientation) {
                        //左右滑动交给ViewPager处理
                    case 'r':
                        setNeedIntercept(false);
                        break;
                    //左右滑动交给ViewPager处理
                    case 'l':
                        setNeedIntercept(false);
                        break;
                }
                return isNeedIntercept;
        }
        return super.onInterceptTouchEvent(e);
    }

    public void setNeedIntercept(boolean needIntercept) {
        isNeedIntercept = needIntercept;
    }

    private int getOrientation(float dx, float dy) {
        if (Math.abs(dx)>Math.abs(dy)){
            //X轴移动
            return dx>0?'r':'l';//右,左
        }else{
            //Y轴移动
            return dy>0?'b':'t';//下//上
        }
    }

isNeedIntercept为是否拦截滑动事件,自己处理.并提供了一个setNeedIntercept方法供外部调用.代码可以看出,横向的滑动直接放行,让ViewPager处理,向上滑动时候如果tab吸顶了且已经滑动到底部,交给内部的RecyclerView处理,否则自己处理.

我们对内层的RecyclerView进行处理,重写其onTouchEvent方法

     @Override
    public boolean onTouchEvent(MotionEvent e) {
        float x= e.getX();
        float y = e.getY();
        switch (e.getAction()){
            case MotionEvent.ACTION_DOWN:
                //将按下时的坐标存储
                downX = x;
                downY = y;
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                        //获取到距离差
                        float dx= x-downX;
                        float dy = y-downY;
               
                        //通过距离差判断方向
                        int orientation = getOrientation(dx, dy);
                        int[] location={0,0};
                        getLocationOnScreen(location);
                        switch (orientation) {
                            case 'b':
                                //内层RecyclerView下拉到最顶部时候不再处理事件
                                if(!canScrollVertically(-1)){
                                    getParent().requestDisallowInterceptTouchEvent(false);
                                    if(needIntercepectListener!=null){
                                        needIntercepectListener.needIntercepect(false);
                                    }
                                }else{
                                    getParent().requestDisallowInterceptTouchEvent(true);
                                    if(needIntercepectListener!=null){
                                        needIntercepectListener.needIntercepect(true);
                                    }
                                }
                                break;
                            case 't':
                            
                                if(location[1]<=maxY){
                                    getParent().requestDisallowInterceptTouchEvent(true);
                                    if(needIntercepectListener!=null){
                                        needIntercepectListener.needIntercepect(true);
                                      
                                    }
                                }else{
                                    getParent().requestDisallowInterceptTouchEvent(false);
                                    if(needIntercepectListener!=null){
                                        needIntercepectListener.needIntercepect(false);
                                        return true;
                                    }
                                }
                                break;
                            case 'r':
                                getParent().requestDisallowInterceptTouchEvent(false);
                                break;
                            //左右滑动交给ViewPager处理
                            case 'l':
                                getParent().requestDisallowInterceptTouchEvent(false);
                                break;
                        }
                        break;
        }
        return super.onTouchEvent(e);
    }



    private int getOrientation(float dx, float dy) {
        if (Math.abs(dx)>Math.abs(dy)){
            //X轴移动
            return dx>0?'r':'l';//右,左
        }else{
            //Y轴移动
            return dy>0?'b':'t';//下//上
        }
    }

    public void setMaxY(int height) {
        this.maxY=height;
    }

    public interface NeedIntercepectListener{
        void needIntercepect(boolean needIntercepect);
    }
    public void setNeedIntercepectListener(NeedIntercepectListener needIntercepectListener) {
        this.needIntercepectListener = needIntercepectListener;
    }

其中的回调是为了告诉外层的RecyclerView需不需要拦截事件.

滑动冲突到这里基本上处理完了,下面说下吸顶的问题,其实只是思路的问题,这里采取的方式是将TabLayout和ViewPager当做一个外层RecyclerView的最后一个item,并且高度为屏幕高度-状态栏高度,这样当外层RecyclerView滑动到底部,Tab看上去是吸顶的.

简单说下:这个demo之前是按真正的吸顶做的,所以文章改动过,哪里说得不清楚的请直接看demo,主要是处理滑动事件冲突,难度不大,纯属抛砖引玉.

最后暴露一个问题,在外层RecyclerView滑动到底部时,需要将触摸事件交给内层的RecyclerView处理时,按照Demo里的处理方式,手指抬起之后重新滑动,内层RecyclerView才能拿到事件,原因是Demo判断外层RecyclerView是否滑动到底部的代码写在onInterceptTouchEvent里面,这个方法并不会实时调用,试过将判断写在onTouchEvent里面,实时判断再调用onInterceptTouchEvent,但是好像因为内层的RecyclerView并没有消费掉事件,所以这么做并没有效果,并没有实时的将触摸事件交给内层RecyclerView处理,这里尝试了很多方式,都不太理想,希望有思路的大佬给指点一下,效果如下:
GIF2.gif

项目地址:Github

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

推荐阅读更多精彩内容