RecyclerView 悬浮标题 如何实现?
//添加 itemDecoration
public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
this.addItemDecoration(decor, -1);
}
看一下ItemDecoration 的源码
public abstract static class ItemDecoration {
public ItemDecoration() {
}
//绘制方法
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDraw(c, parent);
}
/** @deprecated */
@Deprecated
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
//onDraw 执行完毕执行的方法
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDrawOver(c, parent);
}
/** @deprecated */
@Deprecated
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
/** @deprecated */
@Deprecated
public void getItemOffsets(@NonNull Rect outRect, int itemPosition, @NonNull RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
//获取Item 位置信息
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent);
}
}
新建MyItemDecoration 继承ItemDecoration 重载这三个方法
- 获取headerViewHolder 显示位置
- 绘制要显示的headerViewHolder
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDraw(c, parent);
}
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDrawOver(c, parent);
}
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent);
}
private final StickyHeaderAdapter mAdapter; //接口
public interface StickyHeaderAdapter {
int getHeaderId(int position);//获取position
//创建HeaderViewHolder
RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position);
//绑定实现
void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position);
}
private final SparseArray<Rect> mHeaderRectSet = new SparseArray<>(); // 存储Rect 对象
private final HashMap<Integer, RecyclerView.ViewHolder> mHeaderViews = new HashMap<>();//存储 对应position 下的ViewHolder
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
int headerHeight = 0;
//在使用adapterPosition时最好的加上这个判断
if (position != RecyclerView.NO_POSITION) {
//获取到ItemDecoration所需要的高度
View header = getHeader(parent, position);
headerHeight = header.getHeight();
}
outRect.set(0, headerHeight, 0, 0); //修改hearderHeight 高度
}
/**
* 获得自定义的 Header
* @param parent
* @param position
* @return
*/
public View getHeader(RecyclerView parent, int position) {
//根据位置获取每一组的头部id
final int headerId = mAdapter.getHeaderId(position);
//通过头部id,从保存的头部view数组中获取改组的头部view
RecyclerView.ViewHolder holder = mHeaderViews.get(headerId);
//如果为空,就通过adapert创建
if (holder == null) {
//创建HeaderViewHolder
holder = mAdapter.onCreateHeaderViewHolder(parent, position);
View header = holder.itemView;
header.setClickable(true);
header.setFocusable(true);
//绑定数据
mAdapter.onBindHeaderViewHolder(holder, position);
//测量View并且layout
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
//根据父View的MeasureSpec和子view自身的LayoutParams以及padding来获取子View的MeasureSpec
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width);
int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height);
//进行测量
header.measure(childWidth, childHeight);
//根据测量后的宽高放置位置
header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
//将创建好的头部view保存在数组中,避免每次重复创建
mHeaderViews.put(headerId, holder);
}
return holder.itemView;
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
changDrawOver(c, parent);
}
private void changDrawOver(@NotNull Canvas c, @NotNull RecyclerView parent) {
//mHeaderRectSet为存放屏幕上显示的header的点击区域,每次重新绘制头部的时候清空数据
mHeaderRectSet.clear();
final int count = parent.getChildCount();
//遍历屏幕上加载的item
for (int layoutPos = 0; layoutPos < count; layoutPos++) {
final View child = parent.getChildAt(layoutPos);
//获取该item在列表数据中的位置
final int adapterPos = parent.getChildAdapterPosition(child);
//只有在最上面一个item或者有header的item才绘制header
if (adapterPos != RecyclerView.NO_POSITION && (layoutPos == 0 || hasHeader(adapterPos))) {
View header = getHeader(parent, adapterPos);
c.save();
//获取绘制header的起始位置(left,top)
final int left = child.getLeft();
final int top = getHeaderTop(parent, child, header, adapterPos, layoutPos);
//将画布移动到绘制的位置
c.translate(left, top);
//绘制header
header.draw(c);
c.restore();
//保存绘制的header的区域
mHeaderRectSet.put(adapterPos, new Rect(left, top, left + header.getWidth(), top + header.getHeight()));
}
}
}
RecyclerView ItemDecoration 绘制的HeaderViewHolder 如何实现点击事件?
//添加 Item Touch 监听
public void addOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener listener) {
this.mOnItemTouchListeners.add(listener);
}
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView view, @NonNull MotionEvent e) {
//将事件交给GestureDetector类进行处理,通过onSingleTapUp返回的值,判断是否要拦截事件
boolean tapDetectorResponse = this.mTapDetector.onTouchEvent(e);
//如果是点击在header区域,则拦截事件
if (tapDetectorResponse) {
return true;
}
if (e.getAction() == MotionEvent.ACTION_UP) {
int position = mDecor.findHeaderPositionUnder((int) e.getX(), (int) e.getY());
return position != -1;
}
return false;
}
mTapDetector = new GestureDetectorCompat(recyclerView.getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
//根据点击的坐标查找是不是点击在header的区域
int position = mDecor.findHeaderPositionUnder((int) e.getX(), (int) e.getY());
if (position != -1) {
//如果position不等于-1,则表示点击在header区域,然后在判断是否在header需要响应的区域
View header = mDecor.getHeader(mRecyclerView, position);
if (header instanceof ViewGroup) {
int childCount = ((ViewGroup) header).getChildCount();
for (int i = 0; i < childCount; i++) {
View childAt = ((ViewGroup) header).getChildAt(i);
if (mDecor.findHeaderClickView(childAt, (int) e.getX(), (int) e.getY())) {
//如果在header需要响应的区域,该区域的view模拟点击
childAt.performClick();
break;
}
}
}
return true;
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
});
public int findHeaderPositionUnder(int x, int y) {
for (int i = 0; i < mHeaderRectSet.size(); i++) {
Rect rect = mHeaderRectSet.get(mHeaderRectSet.keyAt(i));
if (rect.contains(x, y)) {
return mHeaderRectSet.keyAt(i);
}
}
return -1;
}
public boolean findHeaderClickView(View view, int x, int y) {
if (view == null) return false;
for (int i = 0; i < mHeaderRectSet.size(); i++) {
Rect rect = mHeaderRectSet.get(mHeaderRectSet.keyAt(i));
if (rect.contains(x, y)) {
Rect vRect = new Rect();
// 需要响应点击事件的区域在屏幕上的坐标
vRect.set(rect.left + view.getLeft(), rect.top + view.getTop(), rect.left + view.getLeft() + view.getWidth(), rect.top + view.getTop() + view.getHeight());
return vRect.contains(x, y);
}
}
return false;
}