背景介绍
RecyclerView要实现长按弹出快捷菜单,需要实现ContextMenu.ContextMenuInfo接口,并进行相应的处理。
public static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {
public int position;
public long id;
public RecyclerViewContextMenuInfo(int position, long id) {
this.position = position;
this.id = id;
}
}
@Override
public boolean showContextMenuForChild(View originalView) {
int longPressPosition = getChildAdapterPosition(originalView);
if(longPressPosition >= 0) {
long longPressId = getAdapter().getItemId(longPressPosition);
mContextMenuInfo = new RecyclerViewContextMenuInfo(longPressPosition, longPressId);
return super.showContextMenuForChild(originalView);
}
return false;
}
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
具体实现参考:RecyclerView+ContextMenu实现菜单项。
以上是背景介绍哈!那我遇到的问题是啥呢?最近官网不是增加了ConcatAdapter(MergeAdapter)么,可以自由添加好多Adapter了,当然也可以实现添加首尾布局的功能了。具体用法请看:RecyclerView更新之-ConcatAdapter(MergeAdapter)。
于是我就将想在项目中使用ConcatAdatper.addAdapter()方法来试试,嗯,一试还挺香的,而且RecyclerView.ViewHolder中还提供了getAbsoluteAdapterPosition()和getBindingAdapterPosition()两个方法,用以获取列表中的绝对坐标及各自adapter的绑定坐标。
问题出现
于是问题就来了,当我长按获取条目position的时候,获取到的position是包含HeaderAdapter中的条目的。代码中是这样的:
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
int position = ((EaseRecyclerView.RecyclerViewContextMenuInfo) item.getMenuInfo()).position;
EaseUser user = adapter.getItem(position);
if(item.getItemId() == R.id.action_friend_delete) {
showDeleteDialog(user);
}
onChildContextItemSelected(item, user);
return super.onContextItemSelected(item);
}
对应到扩展的RecyclerView中的逻辑就是:
@Override
public boolean showContextMenuForChild(View originalView) {
int longPressPosition = getChildAdapterPosition(originalView);
if(longPressPosition >= 0) {
long longPressId = getAdapter().getItemId(longPressPosition);
mContextMenuInfo = new RecyclerViewContextMenuInfo(longPressPosition, longPressId);
return super.showContextMenuForChild(originalView);
}
return false;
}
出现问题的就是下面这句代码:
int longPressPosition = getChildAdapterPosition(originalView);
那我们来看看它的怎么实现的
/**
* Return the adapter position that the given child view corresponds to.
*
* @param child Child View to query
* @return Adapter position corresponding to the given view or {@link #NO_POSITION}
*/
public int getChildAdapterPosition(@NonNull View child) {
final ViewHolder holder = getChildViewHolderInt(child);
return holder != null ? holder.getAbsoluteAdapterPosition() : NO_POSITION;
}
嗯,最终调用的就是holder.getAbsoluteAdapterPosition(),所以返回条目在ConcatAdapter的绝对坐标是可以理解的了。
但是项目中,我们一般不需要它的绝对坐标,而且在上面的场景下,返回holder.getBindingAdapterPosition()才是对的。
改造逻辑
那么问题的纠结点就在是不是可以返回相应的binding position了,在RecyclerView中看了看,没有找到相关的方法,看来官方还没有提供相应的方法,目前只能从holder.getBindingAdapterPosition()这里入手了。
那么开始进行吧,先仿照getChildAdapterPosition()提供一个相似的方法,如下:
public int getChildBindingAdapterPosition(@NonNull View child) {
final RecyclerView.ViewHolder holder = getChildViewHolderInt(child);
return holder != null ? holder.getBindingAdapterPosition() : NO_POSITION;
}
但是啊,RecyclerView的getChildViewHolderInt(child)是一个非public的static方法,没有办法直接用。
但是看这个方法的主要目的是返回相应的RecyclerView的ViewHolder,RecyclerView中是有提供的其他的方法的,看来可以换一种其他方法来实现。
实现如下:
//这里的getChildViewHolderInt是自己构造的,RecyclerView的是一个非public的static方法
RecyclerView.ViewHolder getChildViewHolderInt(View child) {
if (child == null) {
return null;
}
return getChildViewHolder(child);
}
而getChildViewHolder()的代码中是这样的:
/**
* Retrieve the {@link ViewHolder} for the given child view.
*
* @param child Child of this RecyclerView to query for its ViewHolder
* @return The child view's ViewHolder
*/
public ViewHolder getChildViewHolder(@NonNull View child) {
final ViewParent parent = child.getParent();
if (parent != null && parent != this) {
throw new IllegalArgumentException("View " + child + " is not a direct child of "
+ this);
}
return getChildViewHolderInt(child);
}
最终调用的就是我们寻找的getChildViewHolderInt(child)方法。到了这里是不是就解决问题了呢?不是的。。。
进一步改造
因为在使用之前,笔者参考的EnhanceRecyclerView这个开源项目做了相应的更改,其中重写了RecyclerView的Adapter,而holder.getBindingAdapterPosition()能够获取到相应的坐标,是在ConcatAdapter中进行处理的,如下:
public int getLocalAdapterPosition(
Adapter<? extends ViewHolder> adapter,
ViewHolder viewHolder,
int globalPosition
) {
......
int itemsBefore = countItemsBefore(wrapper);
// local position is globalPosition - itemsBefore
int localPosition = globalPosition - itemsBefore;
......
return wrapper.adapter.findRelativeAdapterPositionIn(adapter, viewHolder, localPosition);
}
需要计算出长按条目之前的adapter的数目之和:int itemsBefore = countItemsBefore(wrapper)。
所以啊,我们需要对自己的Adapter进行相应的处理。如下:
@Override
public int findRelativeAdapterPositionIn(@NonNull Adapter<? extends RecyclerView.ViewHolder> adapter,
@NonNull RecyclerView.ViewHolder viewHolder, int localPosition) {
if(adapter == this) {
return localPosition;
}else {
if(mAdapter instanceof ConcatAdapter) {
List<? extends Adapter<? extends RecyclerView.ViewHolder>> adapters = ((ConcatAdapter) mAdapter).getAdapters();
int prePosition = 0;
for(int i = 0; i < adapters.size(); i++) {
Adapter<? extends RecyclerView.ViewHolder> curAdapter = adapters.get(i);
if(curAdapter == adapter) {
return localPosition - prePosition;
}else {
prePosition += curAdapter.getItemCount();
}
}
return NO_POSITION;
}
}
return super.findRelativeAdapterPositionIn(adapter, viewHolder, localPosition);
}
嗯,到这里就可以获取到相对坐标了!
最终的相关代码如下:
public class MyRecyclerView extends RecyclerView {
......
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
@Override
public boolean showContextMenuForChild(View originalView) {
int longPressPosition = getChildBindingAdapterPosition(originalView);
if(longPressPosition >= 0) {
long longPressId = getAdapter().getItemId(longPressPosition);
mContextMenuInfo = new RecyclerViewContextMenuInfo(longPressPosition, longPressId);
return super.showContextMenuForChild(originalView);
}
return false;
}
public int getChildBindingAdapterPosition(@NonNull View child) {
final RecyclerView.ViewHolder holder = getChildViewHolderInt(child);
return holder != null ? holder.getBindingAdapterPosition() : NO_POSITION;
}
RecyclerView.ViewHolder getChildViewHolderInt(View child) {
if (child == null) {
return null;
}
return getChildViewHolder(child);
}
......
public class WrapperRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
......
@Override
public int findRelativeAdapterPositionIn(@NonNull Adapter<? extends RecyclerView.ViewHolder> adapter,
@NonNull RecyclerView.ViewHolder viewHolder, int localPosition) {
if(adapter == this) {
return localPosition;
}else {
if(mAdapter instanceof ConcatAdapter) {
List<? extends Adapter<? extends RecyclerView.ViewHolder>> adapters = ((ConcatAdapter) mAdapter).getAdapters();
int prePosition = 0;
for(int i = 0; i < adapters.size(); i++) {
Adapter<? extends RecyclerView.ViewHolder> curAdapter = adapters.get(i);
if(curAdapter == adapter) {
return localPosition - prePosition;
}else {
prePosition += curAdapter.getItemCount();
}
}
return NO_POSITION;
}
}
return super.findRelativeAdapterPositionIn(adapter, viewHolder, localPosition);
}
......
}
public static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {
public int position;
public long id;
public RecyclerViewContextMenuInfo(int position, long id) {
this.position = position;
this.id = id;
}
}
}