【Android視圖效果】分組列表實(shí)現(xiàn)吸頂效果

效果圖

20190212_142252.gif

分析

先來分析一下厘线,可以看到這是一個(gè)按月份分組的2行圖片列表,列表頂部一個(gè)懸浮欄耳璧,會(huì)隨著列表滑動(dòng)而刷新蹬昌,點(diǎn)擊頂部欄皂贩,彈出了一個(gè)篩選框。

思路

1.列表部分

可以用RecyclerView+GridLayoutManager遮精,月份的標(biāo)題欄可以使用多布局

首先是主體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="長(zhǎng)沙會(huì)議圖集(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>

然后是月份標(biāo)題的布局

<?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添怔,關(guān)于adapter多布局的使用,篇幅所限幼驶,這里不再細(xì)述购桑。

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) {
            //標(biāo)題/數(shù)量
            helper.setText(R.id.tv_pictrue_title, item.getTitle() + "(" + item.getPicture_count() + ")");
            //時(shí)間
            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);
    }

}

其中,由于月份的標(biāo)題需要占滿一行缭贡,需要重寫onAttachedToRecyclerViewonViewDetachedFromWindow方法坑资。

public class FullSpanUtil {

    public static void onAttachedToRecyclerView(RecyclerView recyclerView, final RecyclerView.Adapter adapter, final int pinnedHeaderType) {
        // 如果是網(wǎng)格布局袱贮,這里處理標(biāo)簽的布局占滿一行
        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) {
        // 如果是瀑布流布局,這里處理標(biāo)簽的布局占滿一行
        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來判斷該使用哪個(gè)布局跨蟹。
注: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轉(zhuǎn)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//數(shù)據(jù)項(xiàng)除了最后一項(xiàng)
                    || (position == dataEndPosition - 1 && mDrawLastItem)//數(shù)據(jù)項(xiàng)最后一項(xiàng)
                    || (!(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,難點(diǎn)在于如何設(shè)置點(diǎn)擊點(diǎn)擊事件。感謝作者驾锰,提供了一種新的思路 StickyItemDecoration椭豫,這里只說如何使用喳整,原理閱讀作者源碼即可框都。
按照這個(gè)思路魏保,我們可以將頭部布局單獨(dú)出來谓罗,這樣的話檩咱,處理點(diǎn)擊事件就很簡(jiǎn)單。
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 屏蔽點(diǎn)擊事件
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int desireHeight;
        int desireWidth;

        int count = getChildCount();

        if (count != 1) {
            throw new IllegalArgumentException("只允許容器添加1個(gè)子View不翩!");
        }

        final View child = getChildAt(0);
        // 測(cè)量子元素并考慮外邊距
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        // 獲取子元素的布局參數(shù)
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 計(jì)算子元素寬度器钟,取子控件最大寬度
        desireWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
        // 計(jì)算子元素高度
        desireHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

        // 考慮父容器內(nèi)邊距
        desireWidth += getPaddingLeft() + getPaddingRight();
        desireHeight += getPaddingTop() + getPaddingBottom();
        // 嘗試比較建議最小值和期望值的大小并取大值
        desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
        desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
        // 設(shè)置最終測(cè)量值
        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);
    }

    // 生成默認(rèn)的布局參數(shù)
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return super.generateDefaultLayoutParams();
    }

    // 生成布局參數(shù),將布局參數(shù)包裝成我們的
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    // 生成布局參數(shù),從屬性配置中生成我們的布局參數(shù)
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    // 查當(dāng)前布局參數(shù)是否是我們定義的類型這在code聲明布局參數(shù)時(shí)常常用到
    @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) {
                //可見時(shí)
                shcPictrues.scrollChild(offset);
                shcPictrues.setVisibility(View.VISIBLE);
            }

            @Override
            public void onInVisible() {
              //不可見時(shí)
                shcPictrues.reset();
                shcPictrues.setVisibility(View.INVISIBLE);
            }
        });

        shcPictrues.setDataCallback(new StickyHeadContainer.DataCallback() {
            @Override
            public void onDataChange(int pos) {
              //數(shù)據(jù)更新
                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;
    }


    // 當(dāng)我們調(diào)用mRecyclerView.addItemDecoration()方法添加decoration的時(shí)候,RecyclerView在繪制的時(shí)候梳凛,去會(huì)繪制decorator淹接,即調(diào)用該類的onDraw和onDrawOver方法塑悼,
    // 1.onDraw方法先于drawChildren
    // 2.onDrawOver在drawChildren之后厢蒜,一般我們選擇復(fù)寫其中一個(gè)即可郭怪。
    // 3.getItemOffsets 可以通過outRect.set()為每個(gè)Item設(shè)置一定的偏移量,主要用于繪制Decorator促绵。

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        checkCache(parent);

        if (mAdapter == null) {
            // checkCache的話RecyclerView未設(shè)置之前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);

        // 獲取第一個(gè)可見的item位置
        mFirstVisiblePosition = findFirstVisiblePosition(layoutManager);

        // 獲取標(biāo)簽的位置败晴,
        int stickyHeadPosition = findStickyHeadPosition(mFirstVisiblePosition);
        if (stickyHeadPosition >= 0 && mStickyHeadPosition != stickyHeadPosition) {
            // 標(biāo)簽位置有效并且和緩存的位置不同
            mStickyHeadPosition = stickyHeadPosition;
        }
    }

    /**
     * 從傳入位置遞減找出標(biāo)簽的位置
     *
     * @param formPosition
     * @return
     */
    private int findStickyHeadPosition(int formPosition) {

        for (int position = formPosition; position >= 0; position--) {
            // 位置遞減稳懒,只要查到位置是標(biāo)簽场梆,立即返回此位置
            final int type = mAdapter.getItemViewType(position);
            if (isStickyHeadType(type)) {
                return position;
            }
        }

        return -1;
    }

    /**
     * 通過適配器告知類型是否為標(biāo)簽
     *
     * @param type
     * @return
     */
    private boolean isStickyHeadType(int type) {
        return mStickyHeadType == type;
    }

    /**
     * 找出第一個(gè)可見的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;
    }

    /**
     * 找出第一個(gè)完全可見的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對(duì)應(yīng)的位置從而判斷出是否標(biāo)簽類型
     *
     * @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.點(diǎn)擊頂部欄彈窗

這里就偷個(gè)懶纯路,不貼代碼了或油。

最后,就是組裝數(shù)據(jù)然后設(shè)置給pictureAdapter即可驰唬。

完整源碼 PicRvDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末顶岸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叫编,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寇损,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)墩衙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門去扣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羽氮,“玉大人令宿,你說我怎么就攤上這事癞松。” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵闹究,是天一觀的道長(zhǎng)叛买。 經(jīng)常有香客問我椒功,道長(zhǎng)旱眯,這世上最難降的妖魔是什么呀页? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任惨奕,我火速辦了婚禮时肿,結(jié)果婚禮上旦签,老公的妹妹穿的比我還像新娘氮凝。我一直安慰自己幽钢,他們只是感情好界拦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汪诉,像睡著了一般上渴。 火紅的嫁衣襯著肌膚如雪奢米。 梳的紋絲不亂的頭發(fā)上苍日,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天炮温,我揣著相機(jī)與錄音,去河邊找鬼种吸。 笑死艺糜,一個(gè)胖子當(dāng)著我的面吹牛褂微,可吹牛的內(nèi)容都是我干的呀癣。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼响巢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼陪腌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扳躬,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤偶摔,失蹤者是張志新(化名)和其女友劉穎宫仗,沒想到半個(gè)月后办悟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卤橄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年殖属,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姐叁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洗显,死狀恐怖外潜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挠唆,我是刑警寧澤处窥,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站损搬,受9級(jí)特大地震影響碧库,放射性物質(zhì)發(fā)生泄漏柜与。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一嵌灰、第九天 我趴在偏房一處隱蔽的房頂上張望弄匕。 院中可真熱鬧,春花似錦沽瞭、人聲如沸迁匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)城丧。三九已至,卻和暖如春豌鹤,著一層夾襖步出監(jiān)牢的瞬間亡哄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工布疙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚊惯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓灵临,卻偏偏與公主長(zhǎng)得像截型,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子儒溉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容