RecycleView吸頂效果

最近使用app時發現一個RecycleView滑動過程中組佈局吸頂的效果, 記得以前學習ListView的時候也見過類似的效果,由於工作中沒有真正使用過雖然那會看懂了,但是現在一點印象沒有了。週末在家搜索了一下發現實現方案有幾種,找了一種實現效果容易理解的把代碼消化了一遍,順便記錄一下分析過程。

效果圖

抽象類 ItemDecoration 的幾個方法

    public abstract static class ItemDecoration {

        /** 該方法會在RecyclerView的ItemView的內容繪製之前繪製,會顯示在ItemView的底層 */
        public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
            onDraw(c, parent);
        }

        /** 
         * 該方法會在RecyclerView的ItemView內容繪製之後繪製,會顯示在ItemView的上層
         */
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
                @NonNull State state) {
            onDrawOver(c, parent);
        }

        /**
         指定當前View的(left, top, right, bottom) 一般是修改top屬性,給groupView佈局的繪製留出空間
         */
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                @NonNull RecyclerView parent, @NonNull State state) {
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }
    }

View組件顯示的內容通過cache機制保存爲bitmap,使用到的API有

void  setDrawingCacheEnabled(boolean flag)

Bitmap  getDrawingCache(boolean autoScale)

void  buildDrawingCache(boolean autoScale)

void  destroyDrawingCache()

我們要獲取View的cache先要通過setDrawingCacheEnable方法把cache開啓,然後再調用getDrawingCache方法就可以獲得view的cache圖片了。buildDrawingCache方法可以不用調用,因爲調用getDrawingCache方法時,若果 cache沒有建立,系統會自動調用buildDrawingCache方法生成cache。若果要更新cache, 必須要調用destoryDrawingCache方法把舊的cache銷燬,才能建立新的。

當調用setDrawingCacheEnabled方法設置爲false,系統也會自動把原來的cache銷燬。

源碼部分直接貼出代碼, 帶有詳細分析

public class SectionDecoration extends RecyclerView.ItemDecoration {

    private PinGroupListener mPinGroupListener;

    /** 懸浮欄高度 */
    private int mGroupHeight = 80;

    public SectionDecoration(PinGroupListener listener) {
        this.mPinGroupListener = listener;
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        // 得到添加的分割線View的位置
        int pos = parent.getChildAdapterPosition(view);
        String groupId = getGroupName(pos);
        if (TextUtils.isEmpty(groupId)) {
            return;
        }
        // 只有是同一組的第一個才顯示懸浮欄, 即 0 - itemView.top 之間的區域顯示懸浮欄
        if (isFirstInGroup(pos)) {
            outRect.top = mGroupHeight;
        }
    }

    /**
     * 獲取組名
     */
    private String getGroupName(int position) {
        return mPinGroupListener == null ? null : mPinGroupListener.getGroupName(position);
    }

    /**
     * 判斷是不是同一組第一個ItemView
     *
     * @return
     */
    private boolean isFirstInGroup(int pos) {
        return pos == 0 || !TextUtils.equals(getGroupName(pos - 1), getGroupName(pos));
    }

    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        // 獲取數據源的數目
        int itemCount = state.getItemCount();
        // 當前屏幕顯示的Item數目
        int childCount = parent.getChildCount();
        // 得出距離左邊和右邊的padding
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        //
        String preGroupName;
        String currentGroupName = null;
        // 開始繪製當前屏幕的ItemView的GroupView
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            /** 該position是給定子視圖對應的適配器位置 */
            int position = parent.getChildAdapterPosition(view);
            preGroupName = currentGroupName;

            currentGroupName = getGroupName(position);
            // 是否是同一組
            // 1、️在某次上滑的過程中, 當第一個可見的itemView是該組最後一個itemView時,和groupView將會有重合部分, preGroupName = null
            if (currentGroupName == null || TextUtils.equals(currentGroupName, preGroupName)) {
                continue;
            }
            int viewBottom = view.getBottom();
            // top決定當前頂部第一個懸浮Group的位置
            int top = Math.max(mGroupHeight, view.getTop());
            int nextPosition = position + 1;
            if (nextPosition < itemCount) {
                String nextGroupName = getGroupName(nextPosition);
                // 下一組的第一個View接近頭部
                // 2、當頂部的groupView和該組最後一個itemView重合時, viewBottom = top(即 mGroupHeight), 繼續上滑時,viewBottom < top (即 mGroupHeight)
                if (!TextUtils.equals(currentGroupName, nextGroupName) && viewBottom < top) {
                    top = viewBottom; // top - mGroupHeight < 0 -> 部分滑出屏幕
                }
            }
            // 根據position獲取懸浮View
            View groupView = getGroupView(position);
            if (groupView == null) {
                return;
            }

            drawGroupView(c, left, right, view, top, groupView);
        }
    }

    private void drawGroupView(@NonNull Canvas c, int left, int right, View view, int top, View groupView) {
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mGroupHeight);
        groupView.setLayoutParams(layoutParams);
        groupView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        // 指定groupView的高度、寬度
        groupView.layout(0, 0, right, mGroupHeight);

        // View組件顯示的內容通過cache機制保存爲bitmap
        groupView.setDrawingCacheEnabled(true);
        Bitmap bitmap = groupView.getDrawingCache();
        // 系統會自動把原來的cache銷燬
        view.setDrawingCacheEnabled(false);
        c.drawBitmap(bitmap, left, top - mGroupHeight, null);
    }

    private View getGroupView(int position) {
        return mPinGroupListener == null ? null : mPinGroupListener.getGroupView(position);
    }


    public static class Builder {
        SectionDecoration mSectionDecoration;


        private Builder(PinGroupListener listener) {
            mSectionDecoration = new SectionDecoration(listener);
        }

        public static Builder init(PinGroupListener listener) {
            return new Builder(listener);
        }

        /**
         * 設置group高度
         */
        public Builder groupHeight(int groupHeight) {
            mSectionDecoration.mGroupHeight = groupHeight;
            return this;
        }

        public SectionDecoration build() {
            return mSectionDecoration;
        }
    }

    public interface PinGroupListener {

        /** 獲取每一組組名 */
        String getGroupName(int position);

        /** 獲取組View */
        View getGroupView(int position);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章