最近專案中需要實現一個分類頁面
- UI圖
實現要求
- 左側聯動右側: 點選左側列表的某一項,背景變色,同時右側列表中對應的分類滾動到頂部
- 右側列表懸停: 右側列表滑動的時候相應的標題欄需要在頂部懸停
- 標題欄可點選
- 右側聯動左側: 滾動右側列表,監聽滾動的位置,左側列表需要同步選中相應的列表
- 效果圖
對照著上面的UI要求,基本上實現了所有的需求,下面分享一下實現的思路
左側聯動右側
兩側都是Recyclerview,一開始以為就是呼叫一下Recyclerview的scrollToPostion滾動到具體的位置就好,但是實際上並非如此,因為Recyclerview的滾動方法有兩種
- scrollToPosition(int)
但是實際上呼叫的時候就比較坑爹,分為兩種情況
- 從上往下滾動
如果是從上往下滾動的時候,發現每次達不到預期的效果,只能將需要滾動的item的顯示出來而已,並不能滾動到頂部
- 從下滾動
這種情況是OK的,每次能夠達到想要的效果
- scrollBy(int x,int y)
這個方法如果是針對LinearLayoutManager的話,可以動態計算滾動的高度,但是針對相對複雜的佈局就非常麻煩,最後找到了一種解決方案:
- 滾動的position小於FirstVisibleItemPosition
直接呼叫scrollToPosition
mRv.smoothScrollToPosition(n);
複製程式碼
- 滾動的position介於FirstVisibleItemPosition與LastVisibleItemPosition之間
獲取需要滾動的position距離頂部的高度,然後呼叫scrollBy
int top = mRv.getChildAt(n - firstItem).getTop();
mRv.smoothScrollBy(0, top);
複製程式碼
- 滾動的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);
}
複製程式碼