上面两个效果图,图一里面我为了展现吸顶原理,我给吸顶加了背景,图二是取消背景的效果图。
我直接上相关代码,然后再进行解释:
这个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 :
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),这两个刷新都会被吸顶盖住。哎,我心好痛。我想找到或者实现 既能下拉头部刷新 + 吸顶 。。。。