美文网首页Android开发积累Android应用开发那些事高级UI
【Android视图效果】分组列表实现吸顶效果

【Android视图效果】分组列表实现吸顶效果

作者: 欢子3824 | 来源:发表于2019-02-12 15:26 被阅读613次

    效果图

    20190212_142252.gif

    分析

    先来分析一下,可以看到这是一个按月份分组的2行图片列表,列表顶部一个悬浮栏,会随着列表滑动而刷新,点击顶部栏,弹出了一个筛选框。

    思路

    1.列表部分

    可以用RecyclerView+GridLayoutManager,月份的标题栏可以使用多布局

    首先是主体item的布局

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/cffffff"
        app:cardCornerRadius="4dp">
    
    
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/iv_pictrue"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                app:layout_constraintDimensionRatio="1.78:1" />
    
            <TextView
                android:id="@+id/tv_pictrue_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:textColor="@color/c151619"
                android:textSize="16sp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/iv_pictrue"
                tools:text="长沙会议图集(210)" />
    
            <TextView
                android:id="@+id/tv_pictrue_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="2dp"
                android:layout_marginBottom="10dp"
                tools:text="2018-11-10"
                android:textColor="@color/c969696"
                android:textSize="16sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tv_pictrue_title" />
    
    
        </android.support.constraint.ConstraintLayout>
    
    </android.support.v7.widget.CardView>
    

    然后是月份标题的布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingTop="15dp"
        android:paddingBottom="15dp">
    
        <TextView
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="10dp"
            android:layout_weight="1"
            android:background="@color/cbbbfc2" />
    
        <TextView
            android:id="@+id/tv_picture_month"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="2018年10月"
            android:textColor="@color/c969696"
            android:textSize="16sp" />
    
    
        <TextView
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="15dp"
            android:layout_weight="1"
            android:background="@color/cbbbfc2" />
    
    
    </LinearLayout>
    

    PictureAdapter 这里使用了 BaseRecyclerViewAdapterHelper,需要继承BaseMultiItemQuickAdapter,关于adapter多布局的使用,篇幅所限,这里不再细述。

    public class PictureAdapter extends BaseMultiItemQuickAdapter<PictureModel, BaseViewHolder> {
        public PictureAdapter(@Nullable List<PictureModel> data) {
            super(data);
            addItemType(PictureModel.PICTURE_CONTENT, R.layout.item_pictures);
            addItemType(PictureModel.PICTURE_TITLE, R.layout.item_picture_month);
        }
    
    
        @Override
        protected void convert(BaseViewHolder helper, PictureModel item) {
    
            if (helper.getItemViewType() == PictureModel.PICTURE_CONTENT) {
                //标题/数量
                helper.setText(R.id.tv_pictrue_title, item.getTitle() + "(" + item.getPicture_count() + ")");
                //时间
                helper.setText(R.id.tv_pictrue_time, item.getDate());
                //封面图
                GlideUtils.loadImg(mContext, item.getCover_image(), (ImageView) helper.getView(R.id.iv_pictrue));
    
            } else if (helper.getItemViewType() == PictureModel.PICTURE_TITLE) {
                helper.setText(R.id.tv_picture_month, item.getDate());
    
            }
    
        }
    
        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            FullSpanUtil.onAttachedToRecyclerView(recyclerView, this, PictureModel.PICTURE_TITLE);
    
    
        }
    
        @Override
        public void onViewDetachedFromWindow(@NonNull BaseViewHolder holder) {
            super.onViewDetachedFromWindow(holder);
            FullSpanUtil.onViewAttachedToWindow(holder, this, PictureModel.PICTURE_TITLE);
        }
    
    }
    

    其中,由于月份的标题需要占满一行,需要重写onAttachedToRecyclerViewonViewDetachedFromWindow方法。

    public class FullSpanUtil {
    
        public static void onAttachedToRecyclerView(RecyclerView recyclerView, final RecyclerView.Adapter adapter, final int pinnedHeaderType) {
            // 如果是网格布局,这里处理标签的布局占满一行
            final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                final GridLayoutManager.SpanSizeLookup oldSizeLookup = gridLayoutManager.getSpanSizeLookup();
                gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        if (adapter.getItemViewType(position) == pinnedHeaderType) {
                            return gridLayoutManager.getSpanCount();
                        }
                        if (oldSizeLookup != null) {
                            return oldSizeLookup.getSpanSize(position);
                        }
                        return 1;
                    }
                });
            }
        }
    
        public static void onViewAttachedToWindow(RecyclerView.ViewHolder holder, RecyclerView.Adapter adapter, int pinnedHeaderType) {
            // 如果是瀑布流布局,这里处理标签的布局占满一行
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
                final StaggeredGridLayoutManager.LayoutParams slp = (StaggeredGridLayoutManager.LayoutParams) lp;
                slp.setFullSpan(adapter.getItemViewType(holder.getLayoutPosition()) == pinnedHeaderType);
            }
        }
    
    
    }
    

    PictureModel需要继承MultiItemEntity,然后重写getItemType方法,adapter即可通过modeltype来判断该使用哪个布局。
    注:get和set方法这里就不贴了

    public class PictureModel implements MultiItemEntity {
    
        public static final int PICTURE_TITLE = 1;
        public static final int PICTURE_CONTENT = 0;
     
    
        private int type;
        private String id;
        private String title;
        private String date_time;
        private String create_time;
        private String picture_count;
        private String status;
        private String cover_image;
        private String date;
    
         public PictureModel(int type) {
            this.type = type;
        }
        public int getType() {
            return type;
        }
    
        public void setType(int type) {
            this.type = type;
        }
    
        @Override
        public int getItemType() {
            return type;
        }
      
    }
    

    最后,是在Activity使用

     pictureAdapter = new PictureAdapter(null);
     rvPictrues.setLayoutManager(new GridLayoutManager(context, 2));
     SpaceDecoration spaceDecoration = new SpaceDecoration(dp2px(context, 10));
     spaceDecoration.setPaddingStart(false);
     rvPictrues.addItemDecoration(spaceDecoration);
     rvPictrues.setAdapter(pictureAdapter);
     pictureAdapter.bindToRecyclerView(rvPictrues);
    
      /**
         * dp转px
         *
         * @param context
         * @param dpVal
         * @return
         */
        public static int dp2px(Context context, float dpVal) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal,
                    context.getResources().getDisplayMetrics());
        }
    

    SpaceDecoration

    public class SpaceDecoration extends RecyclerView.ItemDecoration {
    
        private int space;
        private int headerCount = -1;
        private int footerCount = Integer.MAX_VALUE;
        private boolean mPaddingEdgeSide = true;
        private boolean mPaddingStart = true;
        private boolean mPaddingHeaderFooter = false;
        private ColorDrawable mColorDrawable;
    
        private boolean mDrawLastItem = true;
        private boolean mDrawHeaderFooter = false;
    
    
        public SpaceDecoration(int space) {
            this.mColorDrawable = new ColorDrawable(Color.parseColor("#e7e7e7"));
            this.space = space;
        }
    
        public SpaceDecoration(int space, int color) {
            this.mColorDrawable = new ColorDrawable(color);
            this.space = space;
        }
    
        public void setPaddingEdgeSide(boolean mPaddingEdgeSide) {
            this.mPaddingEdgeSide = mPaddingEdgeSide;
        }
    
        public void setPaddingStart(boolean mPaddingStart) {
            this.mPaddingStart = mPaddingStart;
        }
    
        public void setPaddingHeaderFooter(boolean mPaddingHeaderFooter) {
            this.mPaddingHeaderFooter = mPaddingHeaderFooter;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int position = parent.getChildAdapterPosition(view);
            int spanCount = 0;
            int orientation = 0;
            int spanIndex = 0;
            int headerCount = 0, footerCount = 0;
            if (parent.getAdapter() instanceof BaseQuickAdapter) {
                headerCount = ((BaseQuickAdapter) parent.getAdapter()).getHeaderLayoutCount();
                footerCount = ((BaseQuickAdapter) parent.getAdapter()).getFooterLayoutCount();
            }
    
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (layoutManager instanceof StaggeredGridLayoutManager) {
                orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
                spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
                spanIndex = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
            } else if (layoutManager instanceof GridLayoutManager) {
                orientation = ((GridLayoutManager) layoutManager).getOrientation();
                spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
                spanIndex = ((GridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
            } else if (layoutManager instanceof LinearLayoutManager) {
                orientation = ((LinearLayoutManager) layoutManager).getOrientation();
                spanCount = 1;
                spanIndex = 0;
            }
    
            /**
             * 普通Item的尺寸
             */
            if ((position >= headerCount && position < parent.getAdapter().getItemCount() - footerCount)) {
    
                if (orientation == VERTICAL) {
                    float expectedWidth = (float) (parent.getWidth() - space * (spanCount + (mPaddingEdgeSide ? 1 : -1))) / spanCount;
                    float originWidth = (float) parent.getWidth() / spanCount;
                    float expectedX = (mPaddingEdgeSide ? space : 0) + (expectedWidth + space) * spanIndex;
                    float originX = originWidth * spanIndex;
                    outRect.left = (int) (expectedX - originX);
                    outRect.right = (int) (originWidth - outRect.left - expectedWidth);
                    if (position - headerCount < spanCount && mPaddingStart) {
                        outRect.top = space;
                    }
                    outRect.bottom = space;
                    return;
                } else {
                    float expectedHeight = (float) (parent.getHeight() - space * (spanCount + (mPaddingEdgeSide ? 1 : -1))) / spanCount;
                    float originHeight = (float) parent.getHeight() / spanCount;
                    float expectedY = (mPaddingEdgeSide ? space : 0) + (expectedHeight + space) * spanIndex;
                    float originY = originHeight * spanIndex;
                    outRect.bottom = (int) (expectedY - originY);
                    outRect.top = (int) (originHeight - outRect.bottom - expectedHeight);
                    if (position - headerCount < spanCount && mPaddingStart) {
                        outRect.left = space;
                    }
                    outRect.right = space;
                    return;
                }
            } else if (mPaddingHeaderFooter) {
                if (orientation == VERTICAL) {
                    outRect.right = outRect.left = mPaddingEdgeSide ? space : 0;
                    outRect.top = mPaddingStart ? space : 0;
                } else {
                    outRect.top = outRect.bottom = mPaddingEdgeSide ? space : 0;
                    outRect.left = mPaddingStart ? space : 0;
                }
            }
    
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    
            if (parent.getAdapter() == null) {
                return;
            }
    
            int orientation = 0;
            int headerCount = 0, footerCount = 0, dataCount;
    
            if (parent.getAdapter() instanceof BaseQuickAdapter) {
                headerCount = ((BaseQuickAdapter) parent.getAdapter()).getHeaderLayoutCount();
                footerCount = ((BaseQuickAdapter) parent.getAdapter()).getFooterLayoutCount();
                dataCount = parent.getAdapter().getItemCount();
            } else {
                dataCount = parent.getAdapter().getItemCount();
            }
            int dataStartPosition = headerCount;
            int dataEndPosition = headerCount + dataCount;
    
    
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            if (layoutManager instanceof StaggeredGridLayoutManager) {
                orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
            } else if (layoutManager instanceof GridLayoutManager) {
                orientation = ((GridLayoutManager) layoutManager).getOrientation();
            } else if (layoutManager instanceof LinearLayoutManager) {
                orientation = ((LinearLayoutManager) layoutManager).getOrientation();
            }
            int start, end;
            if (orientation == OrientationHelper.VERTICAL) {
                start = parent.getPaddingLeft();
                end = parent.getWidth() - parent.getPaddingRight();
            } else {
                start = parent.getPaddingTop();
                end = parent.getHeight() - parent.getPaddingBottom();
            }
    
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(child);
    
                if (position >= dataStartPosition && position < dataEndPosition - 1//数据项除了最后一项
                        || (position == dataEndPosition - 1 && mDrawLastItem)//数据项最后一项
                        || (!(position >= dataStartPosition && position < dataEndPosition) && mDrawHeaderFooter)//header&footer且可绘制
                        ) {
    
                    if (orientation == OrientationHelper.VERTICAL) {
                        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                        int top = child.getBottom() + params.bottomMargin;
                        int bottom = top;
                        mColorDrawable.setBounds(start, top, end, bottom);
                        mColorDrawable.draw(c);
                    } else {
                        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                        int left = child.getRight() + params.rightMargin;
                        int right = left;
                        mColorDrawable.setBounds(left, start, right, end);
                        mColorDrawable.draw(c);
                    }
                }
            }
        }
    }
    

    2.顶部栏部分

    这里可以使用ItemDecoration,难点在于如何设置点击点击事件。感谢作者,提供了一种新的思路 StickyItemDecoration,这里只说如何使用,原理阅读作者源码即可。
    按照这个思路,我们可以将头部布局单独出来,这样的话,处理点击事件就很简单。
    activity布局

    <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
    
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/rv_pictrues"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@color/cf7f8fa"
                    android:nestedScrollingEnabled="false" />
    
                <com.leda.yunke.widget.sticky.StickyHeadContainer
                    android:id="@+id/shc_pictrues"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@color/cffffff">
    
                    <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:gravity="center_horizontal"
                        android:orientation="vertical">
    
                        <TextView
                            android:id="@+id/tv_picture_time"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_marginTop="8dp"
                            android:layout_marginBottom="8dp"
                            android:drawableRight="@mipmap/unfold"
                            android:drawablePadding="10dp"
                            android:gravity="center"
                            android:text="2018年11月"
                            android:textColor="@color/c969696"
                            android:textSize="16sp" />
    
                        <TextView style="@style/line_f3f3f3" />
                    </LinearLayout>
    
    
                </com.leda.yunke.widget.sticky.StickyHeadContainer>
            </RelativeLayout>
    

    StickyHeadContainer

    public class StickyHeadContainer extends ViewGroup {
    
        private int mOffset;
        private int mLastOffset = Integer.MIN_VALUE;
        private int mLastStickyHeadPosition = Integer.MIN_VALUE;
    
        private int mLeft;
        private int mRight;
        private int mTop;
        private int mBottom;
    
        private DataCallback mDataCallback;
    
        public StickyHeadContainer(Context context) {
            this(context, null);
        }
    
        public StickyHeadContainer(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public StickyHeadContainer(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO: 2017/1/9 屏蔽点击事件
                }
            });
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int desireHeight;
            int desireWidth;
    
            int count = getChildCount();
    
            if (count != 1) {
                throw new IllegalArgumentException("只允许容器添加1个子View!");
            }
    
            final View child = getChildAt(0);
            // 测量子元素并考虑外边距
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            // 获取子元素的布局参数
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            // 计算子元素宽度,取子控件最大宽度
            desireWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            // 计算子元素高度
            desireHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
    
            // 考虑父容器内边距
            desireWidth += getPaddingLeft() + getPaddingRight();
            desireHeight += getPaddingTop() + getPaddingBottom();
            // 尝试比较建议最小值和期望值的大小并取大值
            desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
            desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
            // 设置最终测量值
            setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec), resolveSize(desireHeight, heightMeasureSpec));
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
            final View child = getChildAt(0);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            final int paddingLeft = getPaddingLeft();
            final int paddingTop = getPaddingTop();
    
            mLeft = paddingLeft + lp.leftMargin;
            mRight = child.getMeasuredWidth() + mLeft;
    
            mTop = paddingTop + lp.topMargin + mOffset;
            mBottom = child.getMeasuredHeight() + mTop;
    
            child.layout(mLeft, mTop, mRight, mBottom);
        }
    
        // 生成默认的布局参数
        @Override
        protected LayoutParams generateDefaultLayoutParams() {
            return super.generateDefaultLayoutParams();
        }
    
        // 生成布局参数,将布局参数包装成我们的
        @Override
        protected LayoutParams generateLayoutParams(LayoutParams p) {
            return new MarginLayoutParams(p);
        }
    
        // 生成布局参数,从属性配置中生成我们的布局参数
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new MarginLayoutParams(getContext(), attrs);
        }
    
        // 查当前布局参数是否是我们定义的类型这在code声明布局参数时常常用到
        @Override
        protected boolean checkLayoutParams(LayoutParams p) {
            return p instanceof MarginLayoutParams;
        }
    
        public void scrollChild(int offset) {
            if (mLastOffset != offset) {
                mOffset = offset;
                ViewCompat.offsetTopAndBottom(getChildAt(0), mOffset - mLastOffset);
            }
            mLastOffset = mOffset;
        }
    
        protected int getChildHeight() {
            return getChildAt(0).getHeight();
        }
    
        protected void onDataChange(int stickyHeadPosition) {
            if (mDataCallback != null && mLastStickyHeadPosition != stickyHeadPosition) {
                mDataCallback.onDataChange(stickyHeadPosition);
            }
            mLastStickyHeadPosition = stickyHeadPosition;
        }
    
        public void reset() {
            mLastStickyHeadPosition = Integer.MIN_VALUE;
        }
    
        public interface DataCallback {
            void onDataChange(int pos);
    
        }
    
        public void setDataCallback(DataCallback dataCallback) {
            mDataCallback = dataCallback;
        }
    }
    
    

    activity中完整使用

            StickyItemDecoration stickyItemDecoration = new StickyItemDecoration(shcPictrues, PictureModel.PICTURE_TITLE);
            stickyItemDecoration.setOnStickyChangeListener(new OnStickyChangeListener() {
                @Override
                public void onScrollable(int offset) {
                    //可见时
                    shcPictrues.scrollChild(offset);
                    shcPictrues.setVisibility(View.VISIBLE);
                }
    
                @Override
                public void onInVisible() {
                  //不可见时
                    shcPictrues.reset();
                    shcPictrues.setVisibility(View.INVISIBLE);
                }
            });
    
            shcPictrues.setDataCallback(new StickyHeadContainer.DataCallback() {
                @Override
                public void onDataChange(int pos) {
                  //数据更新
                    List<PictureModel> listModels = pictureAdapter.getData();
                    if (listModels.size() > pos) {
                        tvPictureTime.setText(listModels.get(pos).getDate());
                    }
                }
            });
            //添加至rv
            rvPictrues.addItemDecoration(stickyItemDecoration);
    
            pictureAdapter = new PictureAdapter(null);
            rvPictrues.setLayoutManager(new GridLayoutManager(context, 2));
            rvPictrues.addItemDecoration(stickyItemDecoration);
            SpaceDecoration spaceDecoration = new SpaceDecoration(DensityUtils.dp2px(context, 10));
            spaceDecoration.setPaddingStart(false);
            rvPictrues.addItemDecoration(spaceDecoration);
            rvPictrues.setAdapter(pictureAdapter);
            pictureAdapter.bindToRecyclerView(rvPictrues);
    

    StickyItemDecoration

    public class StickyItemDecoration extends RecyclerView.ItemDecoration {
    
        private int mStickyHeadType;
        private int mFirstVisiblePosition;
        //    private int mFirstCompletelyVisiblePosition;
        private int mStickyHeadPosition;
        private int[] mInto;
    
        private RecyclerView.Adapter mAdapter;
    
        private StickyHeadContainer mStickyHeadContainer;
        private boolean mEnableStickyHead = true;
    
    
        private OnStickyChangeListener mOnStickyChangeListener;
        public void setOnStickyChangeListener(OnStickyChangeListener onStickyChangeListener){
            this.mOnStickyChangeListener = onStickyChangeListener;
        }
    
        public StickyItemDecoration(StickyHeadContainer stickyHeadContainer, int stickyHeadType) {
            mStickyHeadContainer = stickyHeadContainer;
            mStickyHeadType = stickyHeadType;
        }
    
    
        // 当我们调用mRecyclerView.addItemDecoration()方法添加decoration的时候,RecyclerView在绘制的时候,去会绘制decorator,即调用该类的onDraw和onDrawOver方法,
        // 1.onDraw方法先于drawChildren
        // 2.onDrawOver在drawChildren之后,一般我们选择复写其中一个即可。
        // 3.getItemOffsets 可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator。
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
    
            checkCache(parent);
    
            if (mAdapter == null) {
                // checkCache的话RecyclerView未设置之前mAdapter为空
                return;
            }
    
            calculateStickyHeadPosition(parent);
    
            if (mEnableStickyHead /*&& mFirstCompletelyVisiblePosition > mStickyHeadPosition*/ && mFirstVisiblePosition >= mStickyHeadPosition && mStickyHeadPosition != -1) {
                View belowView = parent.findChildViewUnder(c.getWidth() / 2, mStickyHeadContainer.getChildHeight() + 0.01f);
                mStickyHeadContainer.onDataChange(mStickyHeadPosition);
                int offset;
                if (isStickyHead(parent, belowView) && belowView.getTop() > 0) {
                    offset = belowView.getTop() - mStickyHeadContainer.getChildHeight();
                } else {
                    offset = 0;
                }
                if (mOnStickyChangeListener!=null){
                    mOnStickyChangeListener.onScrollable(offset);
                }
            } else {
                if (mOnStickyChangeListener!=null){
                    mOnStickyChangeListener.onInVisible();
                }
            }
    
        }
    
        public void enableStickyHead(boolean enableStickyHead) {
            mEnableStickyHead = enableStickyHead;
            if (!mEnableStickyHead) {
                mStickyHeadContainer.setVisibility(View.INVISIBLE);
            }
        }
    
        private void calculateStickyHeadPosition(RecyclerView parent) {
            final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
    
            //        mFirstCompletelyVisiblePosition = findFirstCompletelyVisiblePosition(layoutManager);
    
            // 获取第一个可见的item位置
            mFirstVisiblePosition = findFirstVisiblePosition(layoutManager);
    
            // 获取标签的位置,
            int stickyHeadPosition = findStickyHeadPosition(mFirstVisiblePosition);
            if (stickyHeadPosition >= 0 && mStickyHeadPosition != stickyHeadPosition) {
                // 标签位置有效并且和缓存的位置不同
                mStickyHeadPosition = stickyHeadPosition;
            }
        }
    
        /**
         * 从传入位置递减找出标签的位置
         *
         * @param formPosition
         * @return
         */
        private int findStickyHeadPosition(int formPosition) {
    
            for (int position = formPosition; position >= 0; position--) {
                // 位置递减,只要查到位置是标签,立即返回此位置
                final int type = mAdapter.getItemViewType(position);
                if (isStickyHeadType(type)) {
                    return position;
                }
            }
    
            return -1;
        }
    
        /**
         * 通过适配器告知类型是否为标签
         *
         * @param type
         * @return
         */
        private boolean isStickyHeadType(int type) {
            return mStickyHeadType == type;
        }
    
        /**
         * 找出第一个可见的Item的位置
         *
         * @param layoutManager
         * @return
         */
        private int findFirstVisiblePosition(RecyclerView.LayoutManager layoutManager) {
            int firstVisiblePosition = 0;
            if (layoutManager instanceof GridLayoutManager) {
                firstVisiblePosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
            } else if (layoutManager instanceof LinearLayoutManager) {
                firstVisiblePosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                mInto = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
                ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(mInto);
                firstVisiblePosition = Integer.MAX_VALUE;
                for (int pos : mInto) {
                    firstVisiblePosition = Math.min(pos, firstVisiblePosition);
                }
            }
            return firstVisiblePosition;
        }
    
        /**
         * 找出第一个完全可见的Item的位置
         *
         * @param layoutManager
         * @return
         */
        private int findFirstCompletelyVisiblePosition(RecyclerView.LayoutManager layoutManager) {
            int firstVisiblePosition = 0;
            if (layoutManager instanceof GridLayoutManager) {
                firstVisiblePosition = ((GridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
            } else if (layoutManager instanceof LinearLayoutManager) {
                firstVisiblePosition = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                mInto = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
                ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(mInto);
                firstVisiblePosition = Integer.MAX_VALUE;
                for (int pos : mInto) {
                    firstVisiblePosition = Math.min(pos, firstVisiblePosition);
                }
            }
            return firstVisiblePosition;
        }
    
        /**
         * 检查缓存
         *
         * @param parent
         */
        private void checkCache(final RecyclerView parent) {
    
            final RecyclerView.Adapter adapter = parent.getAdapter();
            if (mAdapter != adapter) {
                mAdapter = adapter;
                // 适配器为null或者不同,清空缓存
                mStickyHeadPosition = -1;
    
                mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
                    @Override
                    public void onChanged() {
                        reset();
                    }
    
                    @Override
                    public void onItemRangeChanged(int positionStart, int itemCount) {
                        reset();
                    }
    
                    @Override
                    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                        reset();
                    }
    
                    @Override
                    public void onItemRangeInserted(int positionStart, int itemCount) {
                        reset();
                    }
    
                    @Override
                    public void onItemRangeRemoved(int positionStart, int itemCount) {
                        reset();
                    }
    
                    @Override
                    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                        reset();
                    }
                });
    
            }
        }
    
        private void reset() {
            mStickyHeadContainer.reset();
        }
    
        /**
         * 查找到view对应的位置从而判断出是否标签类型
         *
         * @param parent
         * @param view
         * @return
         */
        private boolean isStickyHead(RecyclerView parent, View view) {
            final int position = parent.getChildAdapterPosition(view);
            if (position == RecyclerView.NO_POSITION) {
                return false;
            }
            final int type = mAdapter.getItemViewType(position);
            return isStickyHeadType(type);
        }
    
    }
    
    

    3.点击顶部栏弹窗

    这里就偷个懒,不贴代码了。

    最后,就是组装数据然后设置给pictureAdapter即可。

    完整源码 PicRvDemo

    你的认可,是我坚持更新博客的动力,如果觉得有用,就请点个赞,谢谢

    相关文章

      网友评论

        本文标题:【Android视图效果】分组列表实现吸顶效果

        本文链接:https://www.haomeiwen.com/subject/lmckeqtx.html