最近在項目開發當中遇到一個記錄列表的需求,UED設計稿要求有吸附效果,原本想偷懶在網上找個抄一下,可是簡單的看了一下網上的方案都跟業務耦合比較大,不是很想用,就本身寫了一個和業務解耦,即插即用的。git
廢話不說,先看東西github
實現的效果仍是不錯的。緩存
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//StickyItemDecoration 實現吸附效果
recyclerView.addItemDecoration(new StickyItemDecoration());
複製代碼
咱們就從這行代碼開始分析bash
recyclerView.addItemDecoration(new StickyItemDecoration());
複製代碼
首先進入StickyItemDecoration的構造方法ide
public StickyItemDecoration() {
mStickyView = new ExampleStickyView();
initPaint();
}
複製代碼
咦,第一行代碼是什麼意思? 讓咱們點擊進去看看佈局
public class ExampleStickyView implements StickyView {
@Override
public boolean isStickyView(View view) {
return (Boolean) view.getTag();
}
@Override
public int getStickViewType() {
return 11;
}
}
複製代碼
ExampleStickyView 實現了一個叫作StickyView 的接口,而且須要去實現它的兩個方法,那這兩個方法是作什麼的呢?ui
isStickyView方法 是用來判斷傳遞進來的View是不是須要吸附的View,由於我在適配器當中給須要吸附的View設置了一個tag是true,因此這邊代碼判斷若是tag是true就是須要吸附的View。this
getStickViewType方法,由於須要吸附效果的列表通常都會有2個item type,getStickViewType方法就是返回須要吸附View的type是多少。spa
這兩個方法會在ItemDecoration的繪製方onDrawOver法當中用到。設計
接下來就是初始化繪製參數 initPaint();
構造方法結束之後,就要進入到重要的繪製onDrawOver方法
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//獲得當前RecyclerView的佈局管理器
mLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
mCurrentUIFindStickView = false;
for (int m = 0, size = parent.getChildCount(); m < size; m++) {
View view = parent.getChildAt(m);
/**
* 這裏就用到了ExampleStickyView的isStickyView方法
用來判斷是不是須要吸附效果的View
是的話纔會進入到if邏輯當中
*/
if (mStickyView.isStickyView(view)) {
//當前UI當中是否找到了須要吸附的View,此時設置爲true
mCurrentUIFindStickView = true;
//這個方法是獲得吸附View的viewHolder
getStickyViewHolder(parent);
//緩存須要吸附的View在列表當中的下標position
cacheStickyViewPosition(m);
//若是當前吸附的view距離 頂部小於等於0,而後給吸附的View綁定數據,計算View的寬高
if (view.getTop() <= 0) {
bindDataForStickyView(mLayoutManager.findFirstVisibleItemPosition(), parent.getMeasuredWidth());
} else {
//若是大於0,從position緩存中取得當前的position,而後綁定數據,計算View的寬高
if (mStickyPositionList.size() > 0) {
if (mStickyPositionList.size() == 1) {
bindDataForStickyView(mStickyPositionList.get(0), parent.getMeasuredWidth());
} else {
int currentPosition = getStickyViewPositionOfRecyclerView(m);
int indexOfCurrentPosition = mStickyPositionList.lastIndexOf(currentPosition);
bindDataForStickyView(mStickyPositionList.get(indexOfCurrentPosition - 1), parent.getMeasuredWidth());
}
}
}
//計算吸附的View距離頂部的高度
if (view.getTop() > 0 && view.getTop() <= mStickyItemViewHeight) {
mStickyItemViewMarginTop = mStickyItemViewHeight - view.getTop();
} else {
mStickyItemViewMarginTop = 0;
}
//繪製吸附的View
drawStickyItemView(c);
break;
}
}
//若是在當前的列表視圖中沒有找到須要吸附的View
if (!mCurrentUIFindStickView) {
mStickyItemViewMarginTop = 0;
//若是已經滑動到底部了,就綁定最後一個緩存的position的View,這種狀況通常出如今快速滑動列表的時候吸附View出現錯亂,因此須要綁定一下
if (mLayoutManager.findFirstVisibleItemPosition() + parent.getChildCount() == parent.getAdapter().getItemCount()) {
bindDataForStickyView(mStickyPositionList.get(mStickyPositionList.size() - 1), parent.getMeasuredWidth());
}
//繪製View
drawStickyItemView(c);
}
}
複製代碼
上面代碼每一行都有註釋具體是什麼意思,下面我來簡單的闡述一下代碼的思路和具體邏輯。
大體思路就是:
在列表滾動的時候會進入onDrawOver方法,而後循環當前列表的ItemView,若是遇到是吸附的Item View, 經過適配器再根據itemType來建立一個ViewHolder,而且獲得這個ViewHolder的itemView;
循環的時候須要不斷去緩存吸附View所在RecyclerView中的下標位置position,根據View距離頂部的高度來獲得當前吸附View的position;
接下來經過adapter的onBindViewHolder來給ViewHolder的itemView綁定數據,而後計算itemView的寬高,z這樣吸附的View拿到了,數據也綁定好了;
而後再計算距離頂部的高度,把itemView繪製到屏幕上便可。
若是由於在當前列表中沒有找到吸附的itemView(mCurrentUIFindStickView=false),就直接繪製上一個便可。
介紹到這裏,整理流程就通了,上面貼的並不是所有代碼,下面附上源碼的連接
https://github.com/chenpengfei88/StickyItemDecoration
有興趣的朋友能夠看看,也歡迎你們star。