RecyclerView進階(一)RecyclerView實現雙列表聯動

wustor發表於2017-11-06

最近專案中需要實現一個分類頁面

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

相關文章