美文网首页Android技术文章Android效果/自定义安卓墙
RecyclerView进阶(一)RecyclerView实现双

RecyclerView进阶(一)RecyclerView实现双

作者: wustor | 来源:发表于2017-06-29 16:51 被阅读6973次

本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布

最近项目中需要实现一个分类页面

  • UI图
列表联动效果图

实现要求

  1. 左侧联动右侧:
    点击左侧列表的某一项,背景变色,同时右侧列表中对应的分类滚动到顶部
  2. 右侧列表悬停:
    右侧列表滑动的时候相应的标题栏需要在顶部悬停
  3. 标题栏可点击
  4. 右侧联动左侧:
    滚动右侧列表,监听滚动的位置,左侧列表需要同步选中相应的列表
  • 效果图
列表联动效果图

对照着上面的UI要求,基本上实现了所有的需求,下面分享一下实现的思路

左侧联动右侧

两侧都是Recyclerview,一开始以为就是调用一下Recyclerview的scrollToPostion滚动到具体的位置就好,但是实际上并非如此,因为Recyclerview的滚动方法有两种

  • scrollToPosition(int)

但是实际上调用的时候就比较坑爹,分为两种情况

  • 从上往下滚动

如果是从上往下滚动的时候,发现每次达不到预期的效果,只能将需要滚动的item的显示出来而已,并不能滚动到顶部

  • 从下滚动

这种情况是OK的,每次能够达到想要的效果

  • scrollBy(int x,int y)

这个方法如果是针对LinearLayoutManager的话,可以动态计算滚动的高度,但是针对相对复杂的布局就非常麻烦,最后找到了一种解决方案:

  1. 滚动的position小于FirstVisibleItemPosition

直接调用scrollToPosition

 mRv.smoothScrollToPosition(n);
  1. 滚动的position介于FirstVisibleItemPosition与LastVisibleItemPosition之间

获取需要滚动的position距离顶部的高度,然后调用scrollBy

 int top = mRv.getChildAt(n - firstItem).getTop();
    mRv.smoothScrollBy(0, top);
  1. 滚动的position>LastVisibleItemPosition

先调用scrollToPosition将需要滚动的position显示出来,在滚动完成时进行监听,当滚动停止的时候,执行跟2中相同的操作,达到目的

整体代码如下

//通过滚动的类型来进行相应的滚动
 
  private void smoothMoveToPosition(int n) {
        int firstItem = mManager.findFirstVisibleItemPosition();
        int lastItem = mManager.findLastVisibleItemPosition();
        if (n <= firstItem) {
            mRv.smoothScrollToPosition(n);
        } else if (n <= lastItem) {
            int top = mRv.getChildAt(n - firstItem).getTop();
            mRv.smoothScrollBy(0, top);
        } else {
            mRv.smoothScrollToPosition(n);
            move = true;
        }
    }

 //监听第三种情况,滚动停止之后再次进行滚动

    private class RecyclerViewListener extends RecyclerView.OnScrollListener {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (move && newState == RecyclerView.SCROLL_STATE_IDLE) {
                move = false;
                int n = mIndex - mManager.findFirstVisibleItemPosition();
                Log.d("n---->", String.valueOf(n));
                if (0 <= n && n < mRv.getChildCount()) {
                    int top = mRv.getChildAt(n).getTop();
                    Log.d("top--->", String.valueOf(top));
                    mRv.smoothScrollBy(0, top);
                }
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
        }
    }

右侧列表悬停

之前有看过张旭童的关于利用itemDecoration来实现城市列表索引的博客,写的的确是挺好的,唯一遗憾的是itemDecoration实现的头部不支持点击,所以这里换了另外一种思路。

  • Sectioned实现方式

伪造一个头部,给一个标志,然后加入到已有的数据集合中,然后再Recyclerview的Adapter中针对此标志返回标题栏的布局,达到分组的目的

   for (int i = 0; i < data.length; i++) {
            SortBean titleBean = new SortBean(String.valueOf(i));
            titleBean.setTitle(true);//头部设置为true,默认为false
            titleBean.setTag(String.valueOf(i));
            mDatas.add(titleBean);
            for (int j = 0; j < 10; j++) {
                SortBean sortBean = new SortBean(String.valueOf(i + "行" + j + "个"));
                sortBean.setTag(String.valueOf(i));
                mDatas.add(sortBean);
            }

        }
        
  @Override
    protected int getLayoutId(int viewType) {
        return viewType == 0 ? R.layout.item_title : R.layout.item_classify_detail;
    }

    @Override
    public int getItemViewType(int position) {
        return list.get(position).isTitle() ? 0 : 1;
    }

  • 悬停的实现

其实这个利用了itemDecoration,调用RecyclerView.ItemDecoration的onDrawOver中进行绘制一个跟Title一模一样的标题栏就好了,这样就把第一个Section覆盖了,看起
来好像是悬停的感觉

  • 当发现下一个title顶上来的时候,将canvas向上平移,产生一种向上挤压的动画效果
   if (null != tag && !tag.equals(suspensionTag)) {
               if (child.getHeight() + child.getTop() < mTitleHeight) {
                    c.save();
                    flag = true;
                    c.translate(0, child.getHeight() + child.getTop() mTitleHeight);
                }
            }
  • 绘制悬停的头部

这里没有一个一个控件的进行绘制,因为单个绘制比较麻烦,所以直接绘制了整个布局,同时修改了布局中标题栏中的内容

 View topTitleView = mInflater.inflate(R.layout.item_title, parent, false);
        TextView tvTitle = (TextView) topTitleView.findViewById(R.id.tv_title);
        tvTitle.setText("测试数据" + tag);
        
    //进行测量获取MeasureSpec:toDrawWidthSpec,toDrawHeightSpec,代码省略
    
     //依次调用 measure,layout,draw方法,将复杂头部显示在屏幕上。
        topTitleView.measure(toDrawWidthSpec, toDrawHeightSpec);
        topTitleView.layout(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getPaddingLeft() + topTitleView.getMeasuredWidth(), parent.getPaddingTop() + topTitleView.getMeasuredHeight());
        topTitleView.draw(c);//Canvas默认在视图顶部,无需平移,直接绘制

标题栏可点击

由于是采用的布局而不是itemDecoration的实现,所以所有的标题栏都是可以点击跳转的

右侧联动左侧

当右侧悬停的title内容发生变化的时候,通过一个接口进行回调左侧列表即可,比较简单,贴下代码:

    if (!Objects.equals(tag, currentTag)) {
            Log.d("tag---->", String.valueOf(MainActivity.finalNumber));
            currentTag = tag;
            Integer integer = Integer.valueOf(currentTag);
            mCheckListener.check(integer, false);
        }

Demo下载地址

相关文章

网友评论

  • 乔啊良:大佬我用你的dome到项目中但是那个会闪一下怎么解决和RecycleView的装饰器有关系吗
  • 我是少年520:楼主在吗,发现一个小bug 选中左侧的某一项,然后缓慢滑动右侧的列表,当右侧列表滑动到下一项时,左侧的下一项并不会被选中,继续滑动到下一项,左侧选中状态才恢复正常。
  • 一克拉战英:楼主,我想实现点击左边的时候,右边滑动到对应的位置,这种效果怎么实现呢
    一克拉战英:@wustor 找到问题了,应该用smoothScrollToPosition(),感谢大神
    一克拉战英:@wustor 我看到你的代码中已经加了 private void smoothMoveToPosition(int n) {
    int firstItem = mManager.findFirstVisibleItemPosition();
    int lastItem = mManager.findLastVisibleItemPosition();
    Log.d("first--->", String.valueOf(firstItem));
    Log.d("last--->", String.valueOf(lastItem));
    if (n <= firstItem) {
    mRv.scrollToPosition(n);
    } else if (n <= lastItem) {
    Log.d("pos---->", String.valueOf(n) + "VS" + firstItem);
    int top = mRv.getChildAt(n - firstItem).getTop();
    Log.d("top---->", String.valueOf(top));
    mRv.scrollBy(0, top);
    } else {
    mRv.scrollToPosition(n);
    move = true;
    }
    }
    但是为什么滚动定位的效果没实现呢
    wustor:你的意思是缓慢的滑动对吧,如果是的话就直接调用Recyclerview的smooth滑动的方法就行了
  • 0a41f5a78c1f:大佬大佬
  • jayden_cool:可否分享一篇Android应用社会化分享,微信、宝支付支付,百度地图位置分享的文章,如果可以的话就感激不尽了!
    wustor:@jayden_cool 支付宝微信支付有啊,只是我是把他们封装在一起了,单个接入比较麻烦
  • 飞的鱼33:你好,我发现一个小问题,就是一开始进去,点击左边的西药,然后右边的往上滑,左边的菜单名还是停留在西药,直到右边的滑到了保健品,左边的直接从西药跳到了保健品,楼主你看下是不是存在哦~~~
    wustor:@飞的鱼33 我一会儿看一下
  • 4da63fb2322a:你好,如果右侧没有悬停的头部,那怎么联动左面,如果写在右侧的滑动监听里面,快速滑动是有bug的
    wustor:其实联动跟不联动刷新布局是一样的,因为我是根据的右侧的title距离顶部的位置来切换悬停的头部和联动左边的,跟悬不悬停没有关系的,如果不悬停的话就没有了itemdecoration,那么就需要去监听Recyclerview的滑动事件,个人觉得比较麻烦。
  • 飞的鱼33:请问楼主,安卓4.4到6.0的手机,点击左边的item会出现卡顿,特别是目前第一个,点击最后一个的时候,右边的页面布局刷新要最久?楼主有遇到么
    七岁就狠拽:这个卡顿是因为用LinearLayout包裹了RecyclerView使用了weight的关系, 打日志看onCreateViewHolder, 你就会发现RecyclerView在设置adapter的时候就把所有的条目都创建出来了, 没有缓存一说, 更新的时候会卡
    飞的鱼33:@fatchao 我就是把你的数据替换掉了,可能是数据比较多,点击后右边滚动得反应慢了点。还有我把你那个一行最多三个改成了一行一个的,mManager = new GridLayoutManager(mContext, 3);
    //通过isTitle的标志来判断是否是title
    mManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
    return mDatas.get(position).isTitle() ? 3 : 1;
    }
    });
    请问楼主,除了把3改为1,还需要在哪里改动么
    wustor:我这边的数据是一次性拿到的,所以点击左边的item的时候,右边的数据只是滚动到具体的位置而已,不存在刷新布局的情况,如果需要重新请求数据的话,做成京东的那种比较合适
  • 28a130e16a4e:右侧标题栏被itemDecoration覆盖时,不能点击。
    wustor:@goodwinyb 可以点击的呀,我只是在头顶部绘制了一个itemdecoration而已,它是不会影响头部的点击事件的
  • 后来Memory:楼主我不是很明白这段代码是做什么的 。 我注释之后运行效果还是一样的。if (!TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 1).getTag())
    ) {
    //最后一行只有一个item
    Log.e("log","只有一个item");
    tag = mDatas.get(pos).getTag();
    mSuspensionTag = mDatas.get(pos + 1).getTag();
    if (child.getHeight() + child.getTop() < mTitleHeight) {
    canvas.save();
    flag = true;
    canvas.translate(0, child.getHeight() + child.getTop() - mTitleHeight);
    }
    } else if (!TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 2).getTag())) {
    //最后一行有两个item
    Log.e("log","只有两个item");
    tag = mDatas.get(pos).getTag();
    mSuspensionTag = mDatas.get(pos + 2).getTag();
    if (child.getHeight() + child.getTop() < mTitleHeight) {
    canvas.save();
    flag = true;
    canvas.translate(0, child.getHeight() + child.getTop() - mTitleHeight);
    }
    } else if (!TextUtils.equals(mDatas.get(pos).getTag(), mDatas.get(pos + 3).getTag())) {
    //最后一行有3个item
    Log.e("log","只有三个item");
    tag = mDatas.get(pos).getTag();
    mSuspensionTag = mDatas.get(pos + 3).getTag();
    if (child.getHeight() + child.getTop() < mTitleHeight) {
    canvas.save();
    flag = true;
    canvas.translate(0, child.getHeight() + child.getTop() - mTitleHeight);
    }
    }
    后来Memory:@fatchao :blush: 感谢
    wustor:因为我的每一行最多有3个item,实际可能有1个,2个,或者3个,所以就针对这三种情况进行讨论,由于Gridmanager每一次只能获取到每一行的第一个item,所以就需要判断这一行有几个item,然后再比对这一行的最后一个item跟下一个item的tag是否一样,不一样就切换,一样就不管他,而且只需要判断body,不需要管header,因为header实际上在顶部只有一个,只需要根据当前的位置判断是否需要平移而已。
  • 2b9fe62195a7:作者你好,在第一次进入demo,右侧向下滑动,再向上滑动至顶,左边的tab回不到第一个。一通乱点乱滑之后,就可以回到第一个了
    wustor:@fatchao 你说的那个问题我已经修改好了,代码已经上传到github上面去,谢谢提醒:smiley:
    wustor:@WFla 谢谢提醒,在修复中
  • Vander丶:楼主,看了你的Demo,有一个地方不明白,你这个悬停的效果,在西药标签给中成药标签顶掉的时候,然后中成药的标签会闪一下,这个是你故意这么写的么 .

    正常来说粘性头,不应该一直在那个位置么,只是文字变化么.,
    Vander丶:@fatchao 了解下这个
    http://www.jianshu.com/p/fe69a53502ab
    Vander丶:@fatchao 按你的方式,已经完成这个 Demo,总体来说学到了很多,但是这块有个建议就是你用 ItemDerition 实现粘性头这块,我感觉关联度特别大,如果数据刷新的时候,还得需要通知这个类,这里建议换成假头的实现方式,虽然实现的思路一样,但是会比现在要好一些。
    wustor:@Vander丶 这是bug ,我在修复
  • 娃娃要从孩子抓起:功能倒是实现了,但是耦合性有点强啊,想复用有点麻烦,注释也有点少,debug了半天。
    wustor:@娃娃要从孩子抓起 其实就是有些东西封装地比较多,不然代码量太大:smile:
  • 布谷鸟也会编程:int top = mRv.getChildAt(n - firstItem).getTop();
    mRv.smoothScrollBy(0, top);
    getChildAt这个方法当传如的数字大于屏幕上的view数量时,会报空指针错误
    布谷鸟也会编程:@fatchao 谢谢分享~:smile:
    wustor:已经修复,谢谢提醒
  • 微微心凉L:楼主有个BUG 就是左边的list滑倒下边的时候, 左边也向下滑动直接奔溃了, 我是魅族PRO6S 6.0 的
    微微心凉L:@fatchao 嘿嘿嘿不客气 :smiley:
    wustor:最新的版本已经进行了修复,谢谢提醒:smiley:
  • 放开那芒果:写的不错,本来想尝试一步一步跟着写出来的,结果没写出来。。。
    但是我那Demo调试了一下,貌似右边的测试数据列表往上滑无法滑到数据13、14感觉体验起来不怎么爽:sob:
    wustor:因为body的数量不足以覆盖一屏,所以是没法悬停置顶,这个唯一的解决方法就是增加body的数量,现在主流联动菜单,像饿了么也是这么干的
  • 神毓逍遥_0914:快速点击,左边列表。会报 View childAt = rvSort.getChildAt(position - mLinearLayoutManager.findFirstVisibleItemPosition()); childAt 为空指针。 另外,快速滑动右边的列表,内存抖动的很厉害。
    wustor:@好奇无术 什么没出来:smile:
    ghjjjhghh:@fatchao 大哥 我咋没有 我也是按照你写的 难带是数据少的原因没出来
    wustor:空指针已经修复了,谢谢提醒
  • neko_888:楼主 findFirstVisibleItemPosition只能找到每行的第一个item的位置 如果最后一行是两个item你可以看一下是没有平移的 额我就小小的提一下 我理解错了的话请讲一下哈
    wustor:@neko_888 你在左侧的点击事件setData的时候计算数量的时候也要改成11或者12,我有注释的,从左侧到右侧的位置是需要计算的,不是写死的
    neko_888:@fatchao 唔就是你现在每个目录下面的数据都是10个嘛 那你把每个目录下的数据改成11或者12个 判断就会出错啦..
    wustor:没明白你的意思,再描述详细一点:joy:
  • a1cac60e22bc: 楼主右侧item 改为10 去规避bug,这样好吗?
    wustor:@IT小蔡 好的,谢谢提醒,我晚点把数据全部搞成不规则的,重新测试一下
    a1cac60e22bc:@fatchao 每个分类数量是10 ,测试没有问题,试了一下,把分类数量改为小于10,大于10 会出现问题,楼主试一下
    wustor:之前的bug都是跟数量是没有关系的,我左侧的item数量一直都是14,每个分类下面的数量都是10,只是为了测试方便,正常项目中的分类数量当然不会一致,只需要累计当前点击的左侧的item之前总数量传给右侧的列表即可,我在实际项目中也是这么做的,如果你在实际运行中有什么问题可以提出来:smiley:
  • DreamArea:你好楼主,发现个问题请教下,当把这个 mRv.smoothScrollToPosition(n)换成mRv.scrollToPosition(n)这种方式时,这个 mRv.addOnScrollListener(new RecyclerViewListener())不会回调,所以当n >lastItem的时候达不到置顶的效果
    一克拉战英:楼主,我想实现点击左边的时候,右边滑动到对应的位置,这种效果怎么实现呢
    a1cac60e22bc:楼主右侧item 改为10 去规避bug,这样好吗?
    wustor:已经修复,现在是采用scrollTo的方式,你可以下载最新的代码进行查看
  • GoBg:运行demo 直接报错
    wustor: @GoBg 项目都clone下来了吗
    GoBg:@fatchao java.lang.NoClassDefFoundError: java.util.Objects
    at com.fatchao.gangedrecyclerview.ItemHeaderDecoration.onDrawOver(ItemHeaderDecoration.java:118)
    wustor:什么错,贴出来看看
  • db83ecb6a69e:如何解决当悬停后,在点击悬停的title 会发生点击事件响应在下面
    wustor:不需要解决,因为我只是在顶部绘制了itemDecoration而已,itemDecoration不会消费点击事件,别的title都是Recyclerview的item而已,所以点击事件可以直接响应。
  • 此刻灬暗殇:点击14 有问题1213 都悬浮在最上面了
    此刻灬暗殇:我用模拟器测试的还有问题,真机没问题:smile: 谢谢楼主修正
    wustor:已经修复了,除非是最后面的那一个分类数据太少,到达不了,才会导致你点击最后一个时候最后一个不会悬浮
    wustor:都悬浮是什么意思,每次只有一个title是悬浮的
  • 205e883a5fab:小宇牛逼
  • 658b711387da:左边索引显示不下了的时候,当前显示的最后一个索引能不能定位到右侧选中的索引位置?
    还有一个Bug,点击左侧最后一个索引,会定位到最后第二个索引上。
    wustor:@Joseph丶 可以详细描述一下你说的问题吗,如果是这样,我修复一下
    658b711387da:@战国吃熊 右边关系不大,左边索引位置不对。
    db83ecb6a69e:因为距离不够了,所以顶不上去了,你可以加个footView之类的,不够UI上面就不好看了
  • 青宁呀:可以
  • etrnel:😛😛
  • X1a0Yu_:大佬,左侧的数据跨行点击是不是有点问题啊?就是比如数据3,我现在点数据6?
    wustor:@0502Leeyuu丶 你说的这个我知道,可以控制的
    X1a0Yu_:@fatchao 就是现在选择的是数据3,我想看数据6中的详细数据,点击数据6后,它左边的效果是先到数据4,再到数据5,最后到数据6,而如果我现在选择的是数据6,我想看数据3,点击数据3后,又是另一个滑动效果,我不知道这是不是问题,但是我感觉有点不对劲
    wustor:没太明白你的意思,能详细说一下么
  • X1a0Yu_:谢谢 学习
  • 李安杰克:写的不错,赞一个,悬停和联动,目前需求挺广的
    wustor: 谢谢,还需要完善:smiley:

本文标题:RecyclerView进阶(一)RecyclerView实现双

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