RecyclerView實現StickHeader效果
前言
Android通訊錄管理列表會根據使用者的名字首字母分類,同時在滾動過程中分類的字母資訊也會展示在列表的最上方,這種效果叫做StickHeader也有的叫PinnedHead效果,以前在ListView中實現這種效果相對比較麻煩,現在在RecyclerView上嘗試實現這種效果。
實現效果
實現過程
RecyclerView中新加了ItemDecoration功能,這個功能的介面中提供了onDraw和onDrawOver兩個介面,在StickHeader效果中可以看到有隨著列表一起滾動的頭部,也有一致保持在頂部的頭部。對於隨著列表滾動的頭部,實現和前面實現列表內部分隔符方式一致,使用onDraw回撥繪製。對於始終保持在最上方的頭部,它必須要繪製在所有元素的上方,可以在onDrawOver回撥中實現。
繪製分隔符的方法非常簡單這裡不再贅述,現在來考慮如何實現在RecyclerView頂部繪製隨著列表內容改變的頭部資訊。需要考慮當第一個展示的元素為A組,那麼頭部展示的也是A,如果到了A組最後一個,B組第一個在其下面,那麼HeaderA會隨著A組最後一個滾動上去,並且HeaderB會展示在最上方。那麼我們只需要關注RecyclerView裡的第一個和第二個可見列表元素。在開始程式碼之前先說明一下getTop,getBottom的意義,可以看下圖:
明白了實現原理之後可以為RecyclerView定義如下的ItemDecration實現:
public class StickHeaderDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = "StickHeaderDecoration";
private Paint paint;
private SectionCallback callback;
private int titleHeight;
public StickHeaderDecoration(SectionCallback callback) {
paint = new Paint();
paint.setAntiAlias(true );
paint.setDither(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
paint.setTextSize(CommonUtils.dp2px(30));
this.callback = callback;
this.titleHeight = CommonUtils.dp2px(45);
}
// 繪製普通跟隨列表滾動的頭部
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
for (int i = 0, count = parent.getChildCount(); i < count; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
if (callback.isFirstItem(position)) {
paint.setColor(Color.BLUE);
c.drawRect(parent.getPaddingLeft(), view.getTop() - titleHeight,
parent.getWidth() - parent.getPaddingRight(), view.getTop(), paint);
paint.setColor(Color.WHITE);
c.drawText(callback.getTitle(position), parent.getPaddingLeft() + CommonUtils.dp2px(10),
view.getTop() - CommonUtils.dp2px(10), paint);
}
}
}
// 繪製在頂部的頭部
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
View view = parent.getChildAt(0);
View view2 = parent.getChildAt(1);
int position = parent.getChildAdapterPosition(view);
int curGroup = callback.getGroupId(position);
int position2 = parent.getChildAdapterPosition(view2);
int nextGroup = callback.getGroupId(position2);
int textY = titleHeight;
if (curGroup != nextGroup) {
// 如果header已經可以遮住A3,header需要跟隨滾動,否則header始終處於最上方
if (titleHeight > view.getBottom()) {
textY = view.getBottom();
}
}
paint.setColor(Color.BLUE);
c.drawRect(parent.getPaddingLeft(), 0,
parent.getWidth() - parent.getPaddingRight(), textY, paint);
paint.setColor(Color.WHITE);
c.drawText(callback.getTitle(position), parent.getPaddingLeft() + CommonUtils.dp2px(10),
textY - CommonUtils.dp2px(10), paint);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
Log.d(TAG, "getItemOffsets()");
int position = parent.getChildAdapterPosition(view);
if (callback.isFirstItem(position)) {
outRect.top = CommonUtils.dp2px(45);
} else {
outRect.top = 0;
}
}
public interface SectionCallback {
boolean isFirstItem(int position);
int getGroupId(int position);
String getTitle(int position);
}
}
解釋一下SectionCallback,這個介面是使用者傳入的用來區分列表條目所屬的不同分組,同時為每個分組提供自定義的標題,其中的isFirstItem用來確定position位置上的條目是不是分組的第一條資料,這個和展示header有這密切關係。
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new StickHeaderDecoration(new StickHeaderDecoration.SectionCallback() {
@Override
public boolean isFirstItem(int position) {
return position == 0 || position == 4 || position == 7;
}
@Override
public int getGroupId(int position) {
if (position < 4) {
return 0;
} else if (position < 7) {
return 1;
} else {
return 2;
}
}
@Override
public String getTitle(int position) {
return RecyclerAdapter.groups.get(getGroupId(position));
}
}));
recyclerView.setAdapter(new RecyclerAdapter(this));
以上就是實現RecyclerView StickHeader效果的核心程式碼,檢視詳細程式碼請點選檢視程式碼。