RecyclerView 学习之吸顶(不加头部下拉刷新)

时间:2024-05-21 11:50:05

RecyclerView 学习之吸顶(不加头部下拉刷新)

RecyclerView 学习之吸顶(不加头部下拉刷新)

上面两个效果图,图一里面我为了展现吸顶原理,我给吸顶加了背景,图二是取消背景的效果图。

我直接上相关代码,然后再进行解释:

这个activity 的 xml :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!--<com.scwang.smartrefresh.layout.SmartRefreshLayout-->
    <!--android:id="@+id/smart_layout"-->
    <!--android:layout_width="match_parent"-->
    <!--android:layout_height="match_parent">-->

    <!--<com.scwang.smartrefresh.layout.header.ClassicsHeader-->
    <!--android:layout_width="match_parent"-->
    <!--android:layout_height="wrap_content" />-->

    <android.support.v7.widget.RecyclerView
        android:id="@+id/xd_rcv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!--</com.scwang.smartrefresh.layout.SmartRefreshLayout>-->

    <tyj.com.testlib.xd.StickyHeadContainer
        android:id="@+id/stick_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!--android:background="#0a0"-->

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#ccc">
            <!--android:background="#a0a"-->

            <TextView
                android:id="@+id/xd_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="test"
                android:textColor="#fff"
                android:textSize="16sp" />
            <!--android:background="#00a"-->

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp" />
        </FrameLayout>
    </tyj.com.testlib.xd.StickyHeadContainer>

</RelativeLayout>

xml 里面的StickHeadContainer 的代码:

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import tyj.com.yedashenlib.log.CyLogger;

/**
 * @author ChenYe created by on 2019/3/20 0020. 16:27
 **/

public class StickyHeadContainer extends ViewGroup {

    private static final String TAG = "StickyHeadContainer";
    private int mOffset,mLeft,mRight,mTop,mBottom;
    private int mLastOffset = Integer.MIN_VALUE;
    private int mLastStickyHeadPosition = Integer.MIN_VALUE;

    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;
        CyLogger.e(TAG,"desireWidth1:"+desireWidth);

        // 考虑父容器内边距
        desireWidth += (getPaddingLeft() + getPaddingRight());
        desireHeight += (getPaddingTop() + getPaddingBottom());
        CyLogger.e(TAG,"desireWidth2:"+desireWidth);
        // 尝试比较建议最小值和期望值的大小并取大值
        desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
        desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
        // 设置最终测量值
        CyLogger.e(TAG,"desireWidth3:"+desireWidth);
        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;
    }

    public int getChildHeight() {
        return getChildAt(0).getHeight();
    }

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

然后几个adapter , XdAdapter -> BaseStickAdapter ->BaseDataAdapter :这三个adapter 写到一起

 

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

import tyj.com.yedashenlib.log.CyLogger;
import tyj.com.yedashenlib.toast.ToastUtil;
import tyj.com.yedashenlib.widget.recylerview.holder.SuperViewHolder;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe
 *         created by on 2017/11/17 0017. 09:52
 *         <p>
 *         这个是单列的数据baseAdapter:如果你用这个自定义的recyclerView的话,你的使用场景是跟listview
 *         差不多的效果,就是单列的话,就可以写一个adapter来继承这个BaseAdapter,在你写的adapter只
 *         需要对控件设置数据就可以了。这个BaseAdapter的使用范例的adapter是DataAdapter。
 **/

public abstract class BaseDataAdapter<T> extends RecyclerView.Adapter<SuperViewHolder> {

    private static final String TAG = "BaseDataAdapter";
    protected List<T> mDataList = new ArrayList<>();

    @Override
    public int getItemCount() {
        return mDataList == null ? 0 : mDataList.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (null != mDataList && !mDataList.isEmpty()) {
            T t = mDataList.get(position);
            if (t instanceof MultiTypeEntity) {
                return ((MultiTypeEntity) t).getItemType();
            }
        }
        return super.getItemViewType(position);
    }

    @Override
    public SuperViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (setLayout(viewType) instanceof Integer) {
            View view = View.inflate(parent.getContext(), ((Integer) setLayout(viewType)), null);
            return new SuperViewHolder(view);
        } else if (setLayout(viewType) instanceof View) {
            return new SuperViewHolder(((View) setLayout(viewType)));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(SuperViewHolder holder, int position) {
        if (null == mDataList || mDataList.isEmpty() || position > mDataList.size() - 1) {
            return;
        }
        onBindItemHolder(holder, position, mDataList.get(position));
    }

    //这个是用来解决局部刷新数据的
    @Override
    public void onBindViewHolder(SuperViewHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            //主要是这里
            onBindItemHolder(holder, position, payloads);
        }
    }

    /**
     * 这个是继承这个BaseAdapter需要传进来的item布局,可以传资源id,可以传view
     *
     * @param type 多类型,如果你是单一的类型,这里就传0就好了
     * @return
     */
    protected abstract Object setLayout(int type);

    /**
     * 这个是让继承这个BaseAdapter去实现的,然后在这个方法里面去把数据设置到item的
     * 控件上去的
     *
     * @param holder   holder
     * @param position position
     * @param itemData item数据
     */
    protected abstract void onBindItemHolder(SuperViewHolder holder, int position, T itemData);

    public void onBindItemHolder(SuperViewHolder holder, int position, List<Object> payloads) {

    }

    /**
     * 向外部提供的传递数据到adapter里面刷新数据用的
     *
     * @param data 传一个集合进来,那么代表是想刷新整个adapter的数据
     */
    public void setDataList(List<T> data) {
        mDataList.clear();
        mDataList.addAll(data);
        notifyDataSetChanged();
    }

    /**
     * 向外部提供插入一个集合数据到adapter使用的
     *
     * @param data
     */
    public void addDataList(List<T> data) {
        mDataList.addAll(data);
        notifyDataSetChanged();
    }

    /**
     * 在最后面添加一条数据,然后局部刷新一下
     *
     * @param data
     */
    public void insertLast(T data) {
        mDataList.add(data);
        notifyItemInserted(getItemCount());
    }

    /**
     * 返回点击的实体的数据,这个数据是从adapter里面的集合拿到这个数据,position也是adapter里面的,
     * 这样拿出来的数据才是最安全的。
     *
     * @param position
     * @return
     */
    public T getItemData(int position) {
        if (position < mDataList.size()) {
            return mDataList.get(position);
        }
        return null;
    }

    /**
     * 删除指定的那一条数据
     *
     * @param position
     */
    public void remove(int position) {
        this.mDataList.remove(position);
        notifyItemRemoved(position);
        if (position != getDataList().size()) {
            notifyItemRangeChanged(position, this.mDataList.size() - position);
        }
    }

    /**
     * 删除指定区间的
     */
    public void removeBegin(int beginPosition, int endPosition) {
        try {
            if (mDataList.size() > beginPosition && mDataList.size() > endPosition) {
                for (int i = beginPosition; i < endPosition; i++) {
                    mDataList.remove(beginPosition);
                }
            }
            notifyItemMoved(beginPosition, endPosition);
            notifyItemRangeChanged(beginPosition, this.mDataList.size() - beginPosition);
        } catch (Exception e) {
            ToastUtil.newInstance().showToast("出错了!");
            CyLogger.e(TAG, e.getMessage());
        }
    }

    /**
     *
     */
    public List<T> getDataList() {
        return mDataList;
    }

}
import android.support.v7.widget.RecyclerView;

import tyj.com.yedashenlib.widget.recylerview.adapter.BaseDataAdapter;
import tyj.com.yedashenlib.widget.recylerview.holder.SuperViewHolder;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:38
 **/

public abstract class BaseStickyAdapter<T> extends BaseDataAdapter<T> {

    /**
     * title 、 normal,为了抽取,下面的两个类型定义我还是写在了BaseStickyAdapter里面,如果你后续还要增加类型,可以
     * 自己写在继承BaseStickyAdapter的类里面
     */
    public static final int TYPE_TITLE = 2, TYPE_NORMAL = 3;

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        FullSpanUtil.onAttachedToRecyclerView(recyclerView, this, TYPE_TITLE);
    }

    @Override
    public void onViewAttachedToWindow(SuperViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        FullSpanUtil.onViewAttachedToWindow(holder, this, TYPE_TITLE);
    }
}
import android.view.View;

import tyj.com.testlib.R;
import tyj.com.testlib.entity.XdNormalEntity;
import tyj.com.testlib.entity.XdTitleEntity;
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.yedashenlib.widget.recylerview.holder.SuperViewHolder;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:33
 *         TODO 对title 的点击事件,我这里暂时只写了一个,如果你的吸顶布局里面想设置更多的点击事件,你可以自行设置
 **/

public class XdAdapter extends BaseStickyAdapter<MultiTypeEntity> {

    private OnXdTitleClickListener mXdClickListener = null;

    /**
     * 这里有很多点击事件需要设置,要根据实际情况,目前我这里有两个点击事件,所以这个item点击事件有两个,我就
     * 直接写点击事件1和点击事件2了
     *
     * @param listener
     */
    public void setOnItemClickListener(OnXdTitleClickListener listener) {
        mXdClickListener = listener;
    }

    @Override
    protected Object setLayout(int type) {
        int resId;
        switch (type) {
            case BaseStickyAdapter.TYPE_TITLE:
                resId = R.layout.item_multi_type_1;
                break;
            case BaseStickyAdapter.TYPE_NORMAL:
                resId = R.layout.item_multi_type_2;
                break;
            //如果要在多类型的基础上再实现多类型,可以在这里加case
            default:
                resId = R.layout.item_multi_type_1;
                break;
        }
        return resId;
    }

    @Override
    protected void onBindItemHolder(SuperViewHolder holder, int position, MultiTypeEntity itemData) {
        if (holder.getItemViewType() == BaseStickyAdapter.TYPE_TITLE) {
            final String titleName = ((XdTitleEntity) itemData).getTitleName();
            holder.setText(R.id.type_1_tv, titleName);
            if (null != mXdClickListener) {
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mXdClickListener.titleClick("点击了吸顶的顶部title:" + titleName);
                    }
                });
            }
        } else {
            final String desc = ((XdNormalEntity) itemData).getDesc();
            holder.setText(R.id.type_2_tv, desc);
            if (null != mXdClickListener) {
                holder.itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mXdClickListener.clickItem("点击了普通item:" + desc);
                    }
                });
            }
        }
    }

    public interface OnXdTitleClickListener {

        /**
         * 可以在这里自行添加并列的事件
         *
         * @param title msg
         */
        void titleClick(String title);

        /**
         * 普通item点击事件
         *
         * @param msg
         */
        void clickItem(String msg);
    }
}

然后BaseStickAdapter 里面用到了一个Util :

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.ViewGroup;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:41
 **/
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);
        }
    }

}

adapter的 两个xml ,按照先后顺序是type_1的 ,第二个是type_2的xml:

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/type_1_tv"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#ccc"
        android:gravity="center"
        android:text="test"
        android:textColor="#fff"
        android:textSize="16sp" />

</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" />

        <TextView
            android:id="@+id/type_2_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="40dp"
            android:gravity="center"
            android:text="test"
            android:textSize="16sp" />
    </LinearLayout>
</RelativeLayout>

activity代码 :

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import tyj.com.testlib.adapter.XdAdapter;
import tyj.com.testlib.entity.XdNormalEntity;
import tyj.com.testlib.entity.XdTitleEntity;
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.testlib.xd.StickyHeadContainer;
import tyj.com.testlib.xd.StickyItemDecoration;
import tyj.com.yedashenlib.toast.ToastUtil;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/20 0020. 14:46
 *         需要注意,你的StickyHeadContainer 布局需要跟吸顶布局一直,不然会有显示
 **/

public class XdActivity extends Activity {

    private static final String TAG = "XdActivity";
    private TextView mXdTitleTv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xd);
//        final SmartRefreshLayout sm = (SmartRefreshLayout) findViewById(R.id.smart_layout);
        mXdTitleTv = (TextView) findViewById(R.id.xd_tv);
        RecyclerView rcv = (RecyclerView) findViewById(R.id.xd_rcv);
        rcv.setLayoutManager(new LinearLayoutManager(this));
        final StickyHeadContainer stickHeader = (StickyHeadContainer) findViewById(R.id.stick_layout);
        rcv.addItemDecoration(new StickyItemDecoration(stickHeader, BaseStickyAdapter.TYPE_TITLE));
        final XdAdapter adapter = new XdAdapter();
        rcv.setAdapter(adapter);
        adapter.setOnItemClickListener(new XdAdapter.OnXdTitleClickListener() {
            @Override
            public void titleClick(String title) {
                ToastUtil.newInstance().showToast(title);
            }

            @Override
            public void clickItem(String msg) {
                ToastUtil.newInstance().showToast(msg);
            }
        });
        stickHeader.setDataCallback(new StickyHeadContainer.DataCallback() {
            @Override
            public void onDataChange(int pos) {
                XdTitleEntity item = (XdTitleEntity) adapter.getItemData(pos);
                mXdTitleTv.setText(item.getTitleName());
            }
        });
//        sm.setOnRefreshListener(new OnRefreshListener() {
//            @Override
//            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
//                adapter.setDataList(createList());
//                sm.finishRefresh();
//            }
//        });
//
//        sm.autoRefresh(200, 500, 1, false);
        adapter.setDataList(createList());
    }

    private List<MultiTypeEntity> createList() {
        List<MultiTypeEntity> list = new ArrayList<>();
        list.add(new XdTitleEntity("测试title 1"));
        for (int i = 0; i < 4; i++) {
            list.add(new XdNormalEntity("测试文本1_" + i));
        }

        list.add(new XdTitleEntity("测试title 2"));
        for (int i = 0; i < 8; i++) {
            list.add(new XdNormalEntity("测试文本2_" + i));
        }

        list.add(new XdTitleEntity("测试title 3"));
        for (int i = 0; i < 15; i++) {
            list.add(new XdNormalEntity("测试文本3_" + i));
        }
        return list;
    }
}

activity 里面用到的 ItemDecoration 、 两个实体 、实体里面继承的interface :

import android.graphics.Canvas;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;

/**
 * @author ChenYe created by on 2019/3/21 0021. 09:30
 **/
public class StickyItemDecoration extends RecyclerView.ItemDecoration {

    private static final String TAG = "StickyItemDecoration";

    private int mStickyHeadType;
    private int mFirstVisiblePosition;
    private int mStickyHeadPosition;
    private int[] mInto;

    private RecyclerView.Adapter mAdapter;

    private StickyHeadContainer mStickyHeadContainer;
    private boolean mEnableStickyHead = true;

    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 && 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;
            }
            mStickyHeadContainer.scrollChild(offset);
            mStickyHeadContainer.setVisibility(View.VISIBLE);
//            CyLogger.e(TAG, "吸顶可见,高度:" + mStickyHeadContainer.getHeight() + ",宽度:" + mStickyHeadContainer.getWidth());
        } else {
            mStickyHeadContainer.reset();
            mStickyHeadContainer.setVisibility(View.INVISIBLE);
//            CyLogger.e(TAG, "吸顶不可见");
        }

    }

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

}
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:32
 **/

public class XdTitleEntity implements MultiTypeEntity {

    private String titleName;

    public XdTitleEntity(String titleName) {
        this.titleName = titleName;
    }

    public String getTitleName() {
        return titleName;
    }

    public void setTitleName(String titleName) {
        this.titleName = titleName;
    }

    @Override
    public int getItemType() {
        return BaseStickyAdapter.TYPE_TITLE;
    }
}
import tyj.com.testlib.xd.BaseStickyAdapter;
import tyj.com.yedashenlib.widget.recylerview.multitype.MultiTypeEntity;

/**
 * @author ChenYe created by on 2019/3/21 0021. 08:32
 **/

public class XdNormalEntity implements MultiTypeEntity {

    private String desc;

    public XdNormalEntity(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public int getItemType() {
        return BaseStickyAdapter.TYPE_NORMAL;
    }
}
/**
 * @author ChenYe created by on 2019/3/20 0020. 13:50
 **/

public interface MultiTypeEntity {

    /**
     * 返回数据的type
     *
     * @return
     */
    int getItemType();
}

你把这些类拿起来然后自己写成一个小demo应该是可以跑起来的,我是借鉴了好几个大神的demo,然后再综合一下弄出来。

需要注意的地方是activity 的xml :

RecyclerView 学习之吸顶(不加头部下拉刷新)

1) 你自仔细看这个xml 就会感觉  箭头 3标记的像是多余的,但是其实是有原因的,因为箭头 2 标记的 TextView 无法android:layout_width = "match_parent" ,写了就出现bug了。我是不断的加背景颜色才试出来的。所以我用箭头 3 来将箭头 1 的布局撑起来成match_parent ,然后再将textView 居中。

2) 就是这个demo 因为这个吸顶的原理是在recyclerView 上面盖住了一个View  来实现的。所以如果你的recyclerView 支持头部形式的下拉刷新就会出现问题,你下拉的刷新部分会被这个吸顶布局挡住。我看到vLayout 里面是使用的swiperRefreshLayout 。。。哎,,我自己用的是自定义的给RecyclerView 加头部 和 把RecycerlView 包起来(就是github 里面的SmartRefreshLayout),这两个刷新都会被吸顶盖住。哎,我心好痛。我想找到或者实现 既能下拉头部刷新 + 吸顶 。。。。