Android-MergeAdapter闲聊片

3,105 阅读6分钟

今天我们来聊一聊recyclerview``1.2.0版本的新增的MergeAdapter功能

添加依赖如下:

// 现在还是内测版,之后请自行更换成正式版
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'

MergeAdapter

由于RecyclerView只能绑定一个Adapter, 如果要实现不同的 ViewHolder, 所以只能重写 getItemViewType 来实现, 这样的话就会 耦合度有点高,不便于扩展

MergeAdapter 顾名思义 就是将多个 adapter 合并成一个 adapter , 设置给RecyclerView,从而实现 一个 adapter 负责一种布局的渲染(或者是一种业务逻辑的数据渲染),从而实现业务代码解耦

在实际的业务过程中,经常会出现 一个相同的数据列表,嵌套在不同的页面里面;这个时候用MergeAdapter非常合适;

比如 不同页面头部显示的布局不一样,但是下面的数据列表是一样的【推荐/简介/广告等列表(HeaderAdapter)】+ 【评论列表/视频列表(NormalAdapter)】,这样就可以很好的实现不同业务逻辑代码解耦,否则你 必须 把 HeaderAdapter 的代码耦合到 NormalAdapter

还有一种我们非常常用的功能 就是 上拉加载更多 功能;非常适合用MergeAdapter实现

先来简单看看MergeAdapter的用法

class MergeAdapterActivity : AppCompatActivity() {

    private var mergeAdapter: MergeAdapter = MergeAdapter()
    private var headerAdapter: HeaderAdapter = HeaderAdapter()
    private var normalAdapter: NormalAdapter = NormalAdapter()
    private var footAdapter: FootAdapter = FootAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_merge_adapter)
        recycler_view.layoutManager = LinearLayoutManager(this)
        val mergeAdapter = MergeAdapter()
        // 先预先添加一条数据
        headerAdapter.data.add(0)
        normalAdapter.data.add(0)
        footAdapter.data.add(0)
        
        // 先渲染headerAdapter的数据
        mergeAdapter.addAdapter(headerAdapter)
        // 在渲染normalAdapter的数据
        mergeAdapter.addAdapter(normalAdapter)
        // 最后渲染footAdapter的数据
        mergeAdapter.addAdapter(footAdapter)
        recycler_view.adapter = mergeAdapter
    }

	// 给normalAdapter添加数据
    fun addNormalData(view: View) {
        normalAdapter.data.add(normalAdapter.data.size)
        normalAdapter.notifyItemInserted(normalAdapter.data.size - 1)
    }
	
	// 从normalAdapter中删除数据
    fun removeFootData(view: View) {
        normalAdapter.data.removeAt(0)
        normalAdapter.notifyItemRemoved(0)
    }
}

效果图如下

用法很简单,只需要调用addAdapter添加Adapter, 以前的 Adapter 添加、移除刷新数据 该怎么用 还是怎么用,基本没什么变化

MergeAdapter 同时 还有 removeAdapter 方法 移除 Adapter,这样的话 这个 Adapter渲染的试图全部都会被移除掉

如果你看了 MergeAdapter 的源码,就是发现 里面的核心逻辑全是由 MergeAdapterController 实现的

MergeAdapter.Config

MergeAdapter在构造函数中支持设置MergeAdapter.Config

/**
 * Creates a MergeAdapter with the given config and the given adapters in the given order.
 *
 * @param config   The configuration for this MergeAdapter
 * @param adapters The list of adapters to add
 * @see Config.Builder
 */
@SafeVarargs
public MergeAdapter(
        @NonNull Config config,
        @NonNull Adapter<? extends ViewHolder>... adapters) {
    this(config, Arrays.asList(adapters));
}

MergeAdapter.Config 现在可以配置 isolateViewTypesstableIdMode

isolateViewTypes
/**
 * If {@code false}, {@link MergeAdapter} assumes all assigned adapters share a global
 * view type pool such that they use the same view types to refer to the same
 * ....
 */
public final boolean isolateViewTypes;

正如注释上说的isolateViewTypes是用来设置每个Adapter之间是否独立使用自己的 view type pool, false 表示每个Adapter之间共享 view type pooltrue 表示每个Adapter之间使用自己独立的 view type pool; 默认值是 true

我们先来看看简单的代码

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_merge_adapter)
     // 先预先添加一条数据
    headerAdapter.data.add(0)
    normalAdapter.data.add(0)
    footAdapter.data.add(0)
    
    recycler_view1.layoutManager = LinearLayoutManager(this)
    // 设置IsolateViewTypes=false
    val mergeAdapter1 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(false).build())
    mergeAdapter1.addAdapter(headerAdapter)
    mergeAdapter1.addAdapter(normalAdapter)
    mergeAdapter1.addAdapter(footAdapter)
    recycler_view1.adapter = mergeAdapter1

    recycler_view2.layoutManager = LinearLayoutManager(this)
    // 设置IsolateViewTypes=true
    val mergeAdapter2 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(true).build())
    mergeAdapter2.addAdapter(headerAdapter)
    mergeAdapter2.addAdapter(normalAdapter)
    mergeAdapter2.addAdapter(footAdapter)
    recycler_view2.adapter = mergeAdapter2
}

效果图如下:

可见当isolateViewTypes=false时 并且 每个 adapter 的 itemViewType都相同时(默认值都是0), normalAdapter 和 footAdapter 并没用创建自己的ViewHolder; 而是创建的 headerAdapter 的 ViewHolder(HeaderViewHolder背景是红色的, NormalViewHolder背景是蓝色的, FootViewHolder背景是黑色的)

关键源码如下

class MergeAdapterController implements NestedAdapterWrapper.Callback {
	
	...
	
	public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
	    NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType);
	    return wrapper.onCreateViewHolder(parent, globalViewType);
	}
	    
	@NonNull
	@Override
	public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
		// 获取包含globalViewType的Adapter集合
	    List<NestedAdapterWrapper> nestedAdapterWrappers = mGlobalTypeToWrapper.get(
	            globalViewType);
	    if (nestedAdapterWrappers == null || nestedAdapterWrappers.isEmpty()) {
	        throw new IllegalArgumentException("Cannot find the wrapper for global view"
	                + " type " + globalViewType);
	    }
	    // just return the first one since they are shared
	    //返回第一个Adapter; 即之后会调用第一个Adapter的onCreateViewHolder方法创建ViewHolder
	    return nestedAdapterWrappers.get(0);
	}
}

所以 当isolateViewTypes=false 我们要理清楚 到底创建的是哪个 AdapterViewHolder, 这点相对而言比较重要,防止出现莫名其妙的问题

而当isolateViewTypes=true时,每个 adapter 的 都会创建自己的 ViewHolder, 这就不用多介绍了

stableIdMode

stableIdMode 是个枚举,有三个值 NO_STABLE_IDSISOLATED_STABLE_IDSSHARED_STABLE_IDS;主要是用来设置 AdapterhasStableIds的返回值的

class MergeAdapterController implements NestedAdapterWrapper.Callback {

	...
	
	public boolean hasStableIds() {
	    return mStableIdMode != NO_STABLE_IDS;
	}
}

对于这个参数的话,使用默认值NO_STABLE_IDS即可, 平时都没怎么用过,就略过吧

MergeAdapter 限制

  1. 并不能错中复杂的动态显示不同类型的ViewHolder, MergeAdapter是按照每个Adapter的顺序显示数据的;还是需要配合ItemViewType实现复杂的业务场景
  2. 由于LayoutManager是设置在RecyclerView上的,所以每个Adapter的布局方式都是一样的(不能应用不同的LayoutManager)

暂时想到的就这么多, 先这样把...

getItemViewType 可能存在的坑

这里说的是ViewHoldergetItemViewType方法, 不是AdaptergetItemViewType方法

这里说的坑 是针对 androidx.recyclerview:recyclerview:1.2.0-alpha03内测版,如果之后的版本不一样 请忽略

为什么要说它呢,因为它返回的值,可能跟你想象的不一样

当设置isolateViewTypes=true时,我们来代码 及 日志

// Activity源码
class MergeAdapterActivity : AppCompatActivity() {

    private var headerAdapter: HeaderAdapter = HeaderAdapter()
    private var normalAdapter: NormalAdapter = NormalAdapter()
    private var footAdapter: FootAdapter = FootAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_merge_adapter)
			
        headerAdapter.data.addAll(listOf(0, 1, 2))
        normalAdapter.data.addAll(listOf(0, 1, 2))
        footAdapter.data.addAll(listOf(0, 1, 2))
        ...

        recycler_view2.layoutManager = LinearLayoutManager(this)
        val mergeAdapter2 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(true).build())
        mergeAdapter2.addAdapter(headerAdapter)
        mergeAdapter2.addAdapter(normalAdapter)
        mergeAdapter2.addAdapter(footAdapter)
        recycler_view2.adapter = mergeAdapter2
    }
}

// HeaderAdapter源码
class HeaderAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var data: MutableList<Any?> = mutableListOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        Log.i("Adapter", "create HeaderViewHolder, viewType:$viewType")
        return HeaderViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_header, parent, false))
    }
	
	// 自定义ViewType
    override fun getItemViewType(position: Int): Int {
        return if (position == 0) 100 else 200
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.i("Adapter", "HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: ${holder.itemViewType}, adapter getItemViewType: ${getItemViewType(position)}")
        holder.itemView.tv_text.text = "header data: $position, itemViewType: ${holder.itemViewType}"
    }
    ...
}

// NormalAdapter源码
class NormalAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var data: MutableList<Any?> = mutableListOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        Log.i("Adapter", "create NormalViewHolder, viewType:$viewType")
        return NormalViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_normal, parent, false))
    }
	
	// 自定义ViewType
    override fun getItemViewType(position: Int): Int {
        return if (position == 0) 300 else 400
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.i("Adapter", "NormalViewHolder onBindViewHolder, viewHolder getItemViewType: ${holder.itemViewType}, adapter getItemViewType: ${getItemViewType(position)}")
        holder.itemView.tv_text.text = "normal data: $position, itemViewType: ${holder.itemViewType}"
    }
    ...
}

// FootAdapter源码
class FootAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var data: MutableList<Any?> = mutableListOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        Log.i("Adapter", "create FootViewHolder, viewType:$viewType")
        return FootViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_foot, parent, false))
    }
	
	// 自定义ViewType
    override fun getItemViewType(position: Int): Int {
        return if (position == 0) 500 else 600
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.i("Adapter", "FootViewHolder onBindViewHolder, viewHolder getItemViewType: ${holder.itemViewType}, adapter getItemViewType: ${getItemViewType(position)}")
        holder.itemView.tv_text.text = "foot data: $position, itemViewType: ${holder.itemViewType}"
    }
    ...
}

运行后 打印的日志如下:

Adapter: create HeaderViewHolder, viewType:100
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 0, adapter getItemViewType: 100
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 1, adapter getItemViewType: 200
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 1, adapter getItemViewType: 200
Adapter: create NormalViewHolder, viewType:300
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 2, adapter getItemViewType: 300
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 3, adapter getItemViewType: 400
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 3, adapter getItemViewType: 400
Adapter: create FootViewHolder, viewType:500
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 4, adapter getItemViewType: 500
Adapter: create FootViewHolder, viewType:600
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 5, adapter getItemViewType: 600

可见viewHoldergetItemViewType方法返回的值并不是我们自定义的值,而是从0开始递增的值;

等一下分析源码,接下来我们看看isolateViewTypes=false的效果

class MergeAdapterActivity : AppCompatActivity() {

    private var headerAdapter: HeaderAdapter = HeaderAdapter()
    private var normalAdapter: NormalAdapter = NormalAdapter()
    private var footAdapter: FootAdapter = FootAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_merge_adapter)
        headerAdapter.data.addAll(listOf(0, 1, 2))
        normalAdapter.data.addAll(listOf(0, 1, 2))
        footAdapter.data.addAll(listOf(0, 1, 2))

        recycler_view1.layoutManager = LinearLayoutManager(this)
        val mergeAdapter1 = MergeAdapter(MergeAdapter.Config.Builder().setIsolateViewTypes(false).build())
        mergeAdapter1.addAdapter(headerAdapter)
        mergeAdapter1.addAdapter(normalAdapter)
        mergeAdapter1.addAdapter(footAdapter)
        recycler_view1.adapter = mergeAdapter1
    }
}

// adapter 的代码不变
...

打印日志如下:

Adapter: create HeaderViewHolder, viewType:100
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 100, adapter getItemViewType: 100
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 200, adapter getItemViewType: 200
Adapter: create HeaderViewHolder, viewType:200
Adapter: HeaderViewHolder onBindViewHolder, viewHolder getItemViewType: 200, adapter getItemViewType: 200
Adapter: create NormalViewHolder, viewType:300
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 300, adapter getItemViewType: 300
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 400, adapter getItemViewType: 400
Adapter: create NormalViewHolder, viewType:400
Adapter: NormalViewHolder onBindViewHolder, viewHolder getItemViewType: 400, adapter getItemViewType: 400
Adapter: create FootViewHolder, viewType:500
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 500, adapter getItemViewType: 500
Adapter: create FootViewHolder, viewType:600
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 600, adapter getItemViewType: 600
Adapter: create FootViewHolder, viewType:600
Adapter: FootViewHolder onBindViewHolder, viewHolder getItemViewType: 600, adapter getItemViewType: 600

这个时候viewHoldergetItemViewType方法返回的值就是我们自定义的值了

个人认为 可能是 google 的 bug, 因为毕竟还是内测版,等正式版发布之后时候看看是否还有这样的问题

下面 我们来看看源码,为什么会这样??

// 这是RecyclerView的创建ViewHolder的方法, 删掉了大部分代码
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ...
    if (holder == null) {
    	...
    	// 获取ViewType, 这里的mAdapter是MergeAdapter
        final int type = mAdapter.getItemViewType(offsetPosition);
        ...
        if (holder == null) {
            ...
            // 传给createViewHolder
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            ...
        }
    }
    ...
    return holder;
}

// 这是Adapter的createViewHolder方法
@NonNull
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
    try {
        ...
        // 直接将viewType 赋值给 viewholder 的 mItemViewType
        holder.mItemViewType = viewType;
        return holder;
    } finally {
        TraceCompat.endSection();
    }
}

// 这是MergeAdapter 的 getItemViewType方法,
// 最后会调用MergeAdapterController的getItemViewType方法
@Override
public int getItemViewType(int position) {
    return mController.getItemViewType(position);
}

// MergeAdapterController 的 getItemViewType方法
public int getItemViewType(int globalPosition) {
    WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
    // 这里的mWrapper是NestedAdapterWrapper类
    int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition);
    releaseWrapperAndLocalPosition(wrapperAndPos);
    return itemViewType;
}

// NestedAdapterWrapper 的 getItemViewType方法
// 这里的 mViewTypeLookup 是 ViewTypeLookup 接口的实现类
int getItemViewType(int localPosition) {
    return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition));
}

下面我们来看看ViewTypeLookup的定义,及其两个实现类,分别对应IsolatedViewType=trueIsolatedViewType=false

interface ViewTypeLookup {
    // 定义 每个 adapter 自己的viewType 到 全局 viewType 的转换过程
    int localToGlobal(int localType);

    // 定义 全局 viewType 到 每个 adapter 自己的 viewType 的转换过程
    int globalToLocal(int globalType);

    void dispose();
}

先看看IsolatedViewType=true的实现类

class IsolatedViewTypeStorage implements ViewTypeStorage {
    SparseArray<NestedAdapterWrapper> mGlobalTypeToWrapper = new SparseArray<>();

    int mNextViewType = 0;

	// 生成viewType, 从0开始,然后递增
    int obtainViewType(NestedAdapterWrapper wrapper) {
        int nextId = mNextViewType++;
        mGlobalTypeToWrapper.put(nextId, wrapper);
        return nextId;
    }

    class WrapperViewTypeLookup implements ViewTypeLookup {
        private SparseIntArray mLocalToGlobalMapping = new SparseIntArray(1);
        private SparseIntArray mGlobalToLocalMapping = new SparseIntArray(1);
        final NestedAdapterWrapper mWrapper;

        WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
            mWrapper = wrapper;
        }

        @Override
        public int localToGlobal(int localType) {
            int index = mLocalToGlobalMapping.indexOfKey(localType);
            if (index > -1) {
                return mLocalToGlobalMapping.valueAt(index);
            }
            // get a new key.
            // 调用obtainViewType生成globalType
            int globalType = obtainViewType(mWrapper);
            // 缓存起来
            mLocalToGlobalMapping.put(localType, globalType);
            mGlobalToLocalMapping.put(globalType, localType);
            return globalType;
        }
        
        // 直接从mGlobalToLocalMapping缓存中,取出localType,return 出去
        @Override
        public int globalToLocal(int globalType) {
            int index = mGlobalToLocalMapping.indexOfKey(globalType);
            if (index < 0) {
                throw new IllegalStateException("requested global type " + globalType + " does"
                        + " not belong to the adapter:" + mWrapper.adapter);
            }
            return mGlobalToLocalMapping.valueAt(index);
        }
        ...
    }
}

可见当IsolatedViewType=true时,globalType是从0开始 依次递增的

下面看看IsolatedViewType=false时的子类实现

class SharedIdRangeViewTypeStorage implements ViewTypeStorage {
    
	...
    class WrapperViewTypeLookup implements ViewTypeLookup {
        final NestedAdapterWrapper mWrapper;

        WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
            mWrapper = wrapper;
        }
        
        // 直接返回localType, 作为globalType
        @Override
        public int localToGlobal(int localType) {
            ...
            return localType;
        }

        @Override
        public int globalToLocal(int globalType) {
            return globalType;
        }
        
        ...
    }
}

再回头看看MergeAdapterControllergetItemViewType方法

public int getItemViewType(int globalPosition) {
    WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
    // 这里返回的是globalType
    // 当`IsolatedViewType=true`时,这里会返回从0开始,然后自增长的值
    // 当`IsolatedViewType=false`时,这里会返回adapter自己自定义的值
    int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition);
    releaseWrapperAndLocalPosition(wrapperAndPos);
    // 返回出去,赋值给ViewHolder
    // 这就导致了 ViewHolder的值时错的
    return itemViewType;
}

那为什么onCreateViewHolder方法的viewType参数的值没问题呢?那是因为MergeAdapterControlleronCreateViewHolder方法将globalType又转换成了每个 adapter 自己的localType, 源码如下

// MergeAdapterController的onCreateViewHolder源码
public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) {
    NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType);
    return wrapper.onCreateViewHolder(parent, globalViewType);
}

// NestedAdapterWrapper 的 onCreateViewHolder方法
ViewHolder onCreateViewHolder(
        ViewGroup parent,
        int globalViewType) {
    // 将globalViewType 转换成 localType
    int localType = mViewTypeLookup.globalToLocal(globalViewType);
    return adapter.onCreateViewHolder(parent, localType);
}

这个问题的源码分析到这,个人认为 这个问题这可能是个bug 或者 ViewHolder 应该会提供一个新的方法获取viewType的值

注意:这个问题是针对 androidx.recyclerview:recyclerview:1.2.0-alpha03内测版的源码分析,可能在之后的版本, 如果之后的版本源码有变动 或 修复了此问题 请忽略此源码分析

所以 如果在使用 MergeAdapter 的过程中 一定要注意 这个问题,否则会crash 或 出现莫名其妙的问题哟

下面我们来看看 我们常用的获取position的方法

getAdapterPosition vs getLayoutPosition

getAdapterPosition 返回ViewHolder绑定的数据在Adapter中的位置

getLayoutPosition 返回ViewHolder的布局渲染完成后最新的计算位置,和用户所见到的位置一致

getLayoutPositiongetAdapterPosition 通常情况下是一样的; 只有当Adapter的数据发生了变化,并且 Layout 还没来得及绘制的这段时间之内才有可能不一样

上面简单的解释可能还是有点懵逼,下面让我们先做个实验

先试一试 notifyDataSetChanged

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    holder.itemView.tv_text.text = "data: $position"
    holder.itemView.setOnClickListener {
        Log.i("Adapter", "click viewHolder, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}")
        // 直接启动一个子线程,测试一下adapterPosition和layoutPosition的变化
        Thread(Runnable {
            while (true) {
                Log.i("Adapter", "data: $position, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}")
                Thread.sleep(5L)
            }
        }).start()
    }
    
    Handler().postDelayed({
        Log.i("Adapter", "==notifyDataSetChanged==")
        notifyDataSetChanged()
    }, 20)
}

比如点击第三条数据;打印出来的日志如下

Adapter: click viewHolder, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: ==notifyDataSetChanged==
Adapter: data: 2, adapterPosition: -1, layoutPosition: 2
Adapter: data: 2, adapterPosition: -1, layoutPosition: 2
Adapter: data: 2, adapterPosition: -1, layoutPosition: -1
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
...

因为notifyDataSetChanged会要求重新绘制所有的数据, 所以ViewHolder在绘制完成前 不知道绑定的是adapter中哪个position的数据,所以返回-1(NO_POSITION)

反而layoutPosition会短暂的缓存上一次的值,在Layout渲染完成之后 adapterPositionlayoutPosition就一样了

下面我们再来试一试notifyItemRemoved

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    holder.itemView.tv_text.text = "data: $position"
    holder.itemView.setOnClickListener {
        Log.i("Adapter", "click viewHolder, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}")
        // 直接启动一个子线程,测试一下adapterPosition和layoutPosition的变化
        Thread(Runnable {
            while (true) {
                Log.i("Adapter", "data: $position, adapterPosition: ${holder.adapterPosition}, layoutPosition: ${holder.layoutPosition}")
                Thread.sleep(5L)
            }
        }).start()
    }
    
    Handler().postDelayed({
        data.removeAt(0) // 删除掉第一条数据
        Log.i("Adapter", "==notifyItemRemoved==")
        notifyItemRemoved(0)
    }, 20)
}

比如点击第三条数据;打印出来的日志如下

Adapter: click viewHolder, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: data: 2, adapterPosition: 2, layoutPosition: 2
Adapter: ==notifyItemRemoved==
Adapter: data: 2, adapterPosition: 1, layoutPosition: 2
Adapter: data: 2, adapterPosition: 1, layoutPosition: 2
Adapter: data: 2, adapterPosition: 1, layoutPosition: 1
Adapter: data: 2, adapterPosition: 1, layoutPosition: 1
Adapter: data: 2, adapterPosition: 1, layoutPosition: 1

由于RecyclerView可以根据 notifyItemRemoved删除的position, 自动计算其他ViewHolder其绑定的数据在adapter中的位置,所以能立即获取到正确的adapterPosition, 但是Layout还没绘制完成, 所以layoutPosition还是以前的值,只有到Layout绘制完成之后, layoutPosition才是最新的值

getBindingAdapterPosition vs getAbsoluteAdapterPosition

recyclerview``1.2.0开始ViewHolder就新添加了getBindingAdapterPositiongetAbsoluteAdapterPosition两个方法;

同时废弃了上面介绍的getAdapterPosition方法; 源码如下

/**
 * @return {@link #getBindingAdapterPosition()}
 * @deprecated This method is confusing when adapters nest other adapters.
 * If you are calling this in the context of an Adapter, you probably want to call
 * {@link #getBindingAdapterPosition()} or if you want the position as {@link RecyclerView}
 * sees it, you should call {@link #getAbsoluteAdapterPosition()}.
 */
@Deprecated
public final int getAdapterPosition() {
    return getBindingAdapterPosition();
}

可见getAdapterPosition是直接返回的getBindingAdapterPosition的值;

那么getBindingAdapterPositiongetAbsoluteAdapterPosition有什么区别呢?

// activity onCreate源码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
	...
    recycler_view.layoutManager = LinearLayoutManager(this)
	val mergeAdapter = MergeAdapter()
	// 添加HeaderAdapter,并给HeaderAdapter添加5条数据
	mergeAdapter.addAdapter(HeaderAdapter().apply {
	    data.addAll(listOf(1, 2, 3, 4, 5))
	})
	// 添加NormalAdapter,并给NormalAdapter添加5条数据
	mergeAdapter.addAdapter(NormalAdapter().apply {
	    data.addAll(listOf(1, 2, 3, 4, 5))
	})
	recycler_view.adapter = mergeAdapter
}

...

// NormalAdapter onBindViewHolder代码
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    holder.itemView.tv_text.text = "data: $position" 
    holder.itemView.setOnClickListener {
        Log.i("Adapter", "click data: $position, bindPosition: ${holder.bindingAdapterPosition}, absolutePosition: ${holder.absoluteAdapterPosition}, layoutPosition: ${holder.layoutPosition}")
    }
}

当点击NormalAdapter的第一条数据时,打印日志如下

// absolutePosition == 5; 是先渲染了HeaderAdapter的5条数据
Adapter: click data: 0, bindPosition: 0, absolutePosition: 5, layoutPosition: 5

可见getBindingAdapterPosition 返回的是ViewHolder绑定的数据在自身Adapter的位置(不考虑MergeAdapter添加的其它adapter)

getAbsoluteAdapterPosition返回的是ViewHolder绑定的数据在MergeAdapter中的位置(会根据自己Adapterposition,然后加上 其在MergeAdapter中的偏移量)

所以以后在大多数情况下,应该是用getBindingAdapterPosition(代替getAdapterPosition), 如果需要考虑偏移量,则使用getAbsoluteAdapterPosition

至于getLayoutPositiongetAbsoluteAdapterPosition类似,会计算自己adapterMergeAdapter中的偏移量, 返回其在MergeAdapter中的position;

由于老的代码里可能用了getLayoutPosition, 所以在配合MergeAdapter的时候,要注意防止数据越界的问题

好吧,对于MergeAdapter的介绍就到这里了!!!