今天我们来聊一聊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
现在可以配置 isolateViewTypes
和 stableIdMode
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 pool
,true
表示每个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
我们要理清楚 到底创建的是哪个 Adapter
的 ViewHolder
, 这点相对而言比较重要,防止出现莫名其妙的问题
而当isolateViewTypes=true
时,每个 adapter 的 都会创建自己的 ViewHolder, 这就不用多介绍了
stableIdMode
stableIdMode
是个枚举,有三个值 NO_STABLE_IDS
、ISOLATED_STABLE_IDS
、SHARED_STABLE_IDS
;主要是用来设置 Adapter
的 hasStableIds
的返回值的
class MergeAdapterController implements NestedAdapterWrapper.Callback {
...
public boolean hasStableIds() {
return mStableIdMode != NO_STABLE_IDS;
}
}
对于这个参数的话,使用默认值NO_STABLE_IDS
即可, 平时都没怎么用过,就略过吧
MergeAdapter 限制
- 并不能错中复杂的动态显示不同类型的
ViewHolder
,MergeAdapter
是按照每个Adapter
的顺序显示数据的;还是需要配合ItemViewType
实现复杂的业务场景 - 由于
LayoutManager
是设置在RecyclerView
上的,所以每个Adapter
的布局方式都是一样的(不能应用不同的LayoutManager
)
暂时想到的就这么多, 先这样把...
getItemViewType 可能存在的坑
这里说的是ViewHolder
的 getItemViewType
方法, 不是Adapter
的getItemViewType
方法
这里说的坑 是针对 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
可见viewHolder
的getItemViewType
方法返回的值并不是我们自定义的值,而是从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
这个时候viewHolder
的getItemViewType
方法返回的值就是我们自定义的值了
个人认为 可能是 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=true
和 IsolatedViewType=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;
}
...
}
}
再回头看看MergeAdapterController
的getItemViewType
方法
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
参数的值没问题呢?那是因为MergeAdapterController
在onCreateViewHolder
方法将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
的布局渲染完成后最新的计算位置,和用户所见到的位置一致
getLayoutPosition
和 getAdapterPosition
通常情况下是一样的; 只有当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
渲染完成之后 adapterPosition
和layoutPosition
就一样了
下面我们再来试一试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
就新添加了getBindingAdapterPosition
和getAbsoluteAdapterPosition
两个方法;
同时废弃了上面介绍的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
的值;
那么getBindingAdapterPosition
和getAbsoluteAdapterPosition
有什么区别呢?
// 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
中的位置(会根据自己Adapter
的position
,然后加上 其在MergeAdapter
中的偏移量)
所以以后在大多数情况下,应该是用getBindingAdapterPosition
(代替getAdapterPosition
), 如果需要考虑偏移量,则使用getAbsoluteAdapterPosition
至于getLayoutPosition
跟getAbsoluteAdapterPosition
类似,会计算自己adapter
在MergeAdapter
中的偏移量, 返回其在MergeAdapter
中的position
;
由于老的代码里可能用了getLayoutPosition
, 所以在配合MergeAdapter
的时候,要注意防止数据越界的问题
好吧,对于MergeAdapter的介绍就到这里了!!!