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);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值