1. 程式人生 > >RecyclerView實現StickHeader效果

RecyclerView實現StickHeader效果

前言

Android通訊錄管理列表會根據使用者的名字首字母分類,同時在滾動過程中分類的字母資訊也會展示在列表的最上方,這種效果叫做StickHeader也有的叫PinnedHead效果,以前在ListView中實現這種效果相對比較麻煩,現在在RecyclerView上嘗試實現這種效果。

實現效果

StickHeader效果

實現過程

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效果的核心程式碼,檢視詳細程式碼請點選檢視程式碼