本篇文章已授權(quán)微信公眾號(hào) hongyangAndroid (鴻洋)獨(dú)家發(fā)布
最近項(xiàng)目中需要實(shí)現(xiàn)一個(gè)分類(lèi)頁(yè)面
- UI圖
實(shí)現(xiàn)要求
- 左側(cè)聯(lián)動(dòng)右側(cè):
點(diǎn)擊左側(cè)列表的某一項(xiàng)饼问,背景變色乾闰,同時(shí)右側(cè)列表中對(duì)應(yīng)的分類(lèi)滾動(dòng)到頂部 - 右側(cè)列表懸停:
右側(cè)列表滑動(dòng)的時(shí)候相應(yīng)的標(biāo)題欄需要在頂部懸停 - 標(biāo)題欄可點(diǎn)擊
- 右側(cè)聯(lián)動(dòng)左側(cè):
滾動(dòng)右側(cè)列表默刚,監(jiān)聽(tīng)滾動(dòng)的位置腺怯,左側(cè)列表需要同步選中相應(yīng)的列表
- 效果圖
對(duì)照著上面的UI要求轨奄,基本上實(shí)現(xiàn)了所有的需求裳仆,下面分享一下實(shí)現(xiàn)的思路
左側(cè)聯(lián)動(dòng)右側(cè)
兩側(cè)都是Recyclerview溉箕,一開(kāi)始以為就是調(diào)用一下Recyclerview的scrollToPostion滾動(dòng)到具體的位置就好晦墙,但是實(shí)際上并非如此,因?yàn)镽ecyclerview的滾動(dòng)方法有兩種
- scrollToPosition(int)
但是實(shí)際上調(diào)用的時(shí)候就比較坑爹肴茄,分為兩種情況
- 從上往下滾動(dòng)
如果是從上往下滾動(dòng)的時(shí)候晌畅,發(fā)現(xiàn)每次達(dá)不到預(yù)期的效果,只能將需要滾動(dòng)的item的顯示出來(lái)而已寡痰,并不能滾動(dòng)到頂部
- 從下滾動(dòng)
這種情況是OK的抗楔,每次能夠達(dá)到想要的效果
- scrollBy(int x,int y)
這個(gè)方法如果是針對(duì)LinearLayoutManager的話(huà),可以動(dòng)態(tài)計(jì)算滾動(dòng)的高度拦坠,但是針對(duì)相對(duì)復(fù)雜的布局就非常麻煩连躏,最后找到了一種解決方案:
- 滾動(dòng)的position小于FirstVisibleItemPosition
直接調(diào)用scrollToPosition
mRv.smoothScrollToPosition(n);
- 滾動(dòng)的position介于FirstVisibleItemPosition與LastVisibleItemPosition之間
獲取需要滾動(dòng)的position距離頂部的高度,然后調(diào)用scrollBy
int top = mRv.getChildAt(n - firstItem).getTop();
mRv.smoothScrollBy(0, top);
- 滾動(dòng)的position>LastVisibleItemPosition
先調(diào)用scrollToPosition將需要滾動(dòng)的position顯示出來(lái)贞滨,在滾動(dòng)完成時(shí)進(jìn)行監(jiān)聽(tīng)入热,當(dāng)滾動(dòng)停止的時(shí)候,執(zhí)行跟2中相同的操作晓铆,達(dá)到目的
整體代碼如下
//通過(guò)滾動(dòng)的類(lèi)型來(lái)進(jìn)行相應(yīng)的滾動(dòng)
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;
}
}
//監(jiān)聽(tīng)第三種情況才顿,滾動(dòng)停止之后再次進(jìn)行滾動(dòng)
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);
}
}
右側(cè)列表懸停
之前有看過(guò)張旭童的關(guān)于利用itemDecoration來(lái)實(shí)現(xiàn)城市列表索引的博客,寫(xiě)的的確是挺好的尤蒿,唯一遺憾的是itemDecoration實(shí)現(xiàn)的頭部不支持點(diǎn)擊郑气,所以這里換了另外一種思路。
- Sectioned實(shí)現(xiàn)方式
偽造一個(gè)頭部,給一個(gè)標(biāo)志腰池,然后加入到已有的數(shù)據(jù)集合中尾组,然后再Recyclerview的Adapter中針對(duì)此標(biāo)志返回標(biāo)題欄的布局,達(dá)到分組的目的
for (int i = 0; i < data.length; i++) {
SortBean titleBean = new SortBean(String.valueOf(i));
titleBean.setTitle(true);//頭部設(shè)置為true,默認(rèn)為false
titleBean.setTag(String.valueOf(i));
mDatas.add(titleBean);
for (int j = 0; j < 10; j++) {
SortBean sortBean = new SortBean(String.valueOf(i + "行" + j + "個(gè)"));
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;
}
- 懸停的實(shí)現(xiàn)
其實(shí)這個(gè)利用了itemDecoration示弓,調(diào)用RecyclerView.ItemDecoration的onDrawOver中進(jìn)行繪制一個(gè)跟Title一模一樣的標(biāo)題欄就好了讳侨,這樣就把第一個(gè)Section覆蓋了,看起
來(lái)好像是懸停的感覺(jué)
- 當(dāng)發(fā)現(xiàn)下一個(gè)title頂上來(lái)的時(shí)候奏属,將canvas向上平移跨跨,產(chǎn)生一種向上擠壓的動(dòng)畫(huà)效果
if (null != tag && !tag.equals(suspensionTag)) {
if (child.getHeight() + child.getTop() < mTitleHeight) {
c.save();
flag = true;
c.translate(0, child.getHeight() + child.getTop() mTitleHeight);
}
}
- 繪制懸停的頭部
這里沒(méi)有一個(gè)一個(gè)控件的進(jìn)行繪制,因?yàn)閱蝹€(gè)繪制比較麻煩,所以直接繪制了整個(gè)布局勇婴,同時(shí)修改了布局中標(biāo)題欄中的內(nèi)容
View topTitleView = mInflater.inflate(R.layout.item_title, parent, false);
TextView tvTitle = (TextView) topTitleView.findViewById(R.id.tv_title);
tvTitle.setText("測(cè)試數(shù)據(jù)" + tag);
//進(jìn)行測(cè)量獲取MeasureSpec:toDrawWidthSpec忱嘹,toDrawHeightSpec,代碼省略
//依次調(diào)用 measure,layout,draw方法耕渴,將復(fù)雜頭部顯示在屏幕上拘悦。
topTitleView.measure(toDrawWidthSpec, toDrawHeightSpec);
topTitleView.layout(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getPaddingLeft() + topTitleView.getMeasuredWidth(), parent.getPaddingTop() + topTitleView.getMeasuredHeight());
topTitleView.draw(c);//Canvas默認(rèn)在視圖頂部,無(wú)需平移橱脸,直接繪制
標(biāo)題欄可點(diǎn)擊
由于是采用的布局而不是itemDecoration的實(shí)現(xiàn)础米,所以所有的標(biāo)題欄都是可以點(diǎn)擊跳轉(zhuǎn)的
右側(cè)聯(lián)動(dòng)左側(cè)
當(dāng)右側(cè)懸停的title內(nèi)容發(fā)生變化的時(shí)候,通過(guò)一個(gè)接口進(jìn)行回調(diào)左側(cè)列表即可添诉,比較簡(jiǎn)單屁桑,貼下代碼:
if (!Objects.equals(tag, currentTag)) {
Log.d("tag---->", String.valueOf(MainActivity.finalNumber));
currentTag = tag;
Integer integer = Integer.valueOf(currentTag);
mCheckListener.check(integer, false);
}