RecyclerView进阶RecyclerView实现双列表联动

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

  • 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下载地址

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值