notifyDataSetChanged
notifyItemChanged(int position)
notifyItemChanged(int position, @Nullable Object payload)
一般来说一个 item 是由多个控件组成的,如 Button、CheckBox、TextView、ImageView、ViewGroup 等组合。当我们点击item的某个控件时,RV 需要重新计算布局、刷新视图来响应交互。假设一个 item 包含了N多个控件,如果调用
notifyItemChanged(int position)
时,item 中的每个控件都需要重新布局显示,无形中加大了内存和性能的损耗。
就算我们不考虑内存和性能的问题,那么一些效果也是我们无法接受的,当 item 刷新的时候会导致内部的图片或item 出现一次闪烁。
以我的精神粮食起点为例,如下
所以我们才需要用到类似 Payload 与 Diff 之类的刷新方式。
下面我们就一起看看它们是怎么使用的。
Payload的刷新
我们通过
notifyItemChanged(int position, @Nullable Object payload)
来刷新指定索引的item。
在 RV 的 Adapter 中的
onBindViewHolder
可以接收到 payloads 参数,这个 payloads 参数是一个 List 对象,该对象不是 null 但可能是空的。
public
void
onBindViewHolder
(VH holder,
int
position, List<Object> payloads)
{
onBindViewHolder(holder, position);
通过 Adapter 的 notifyXXX 函数的带有 payload 参数的函数可以设置 payload 对象,如:
public
final
void
notifyItemChanged
(
int
position, Object payload)
{
mObservable.notifyItemRangeChanged(position,
1
, payload);
由于
onBindViewHolder
有重载的方法,如果使用了 payloads 的方式,那么我们需要做兼容,如果没有 payloads 就去走整个 item 的刷新,如果有 payloads 那么我们就根据指定的 payload 去刷新指定的数据。
@Override
public
void
onBindViewHolder(ViewHolder holder,
int
position,
List
<
Object
> payloads) {
if
(!payloads.isEmpty && payloads.
get
(
0
).equals(
"like"
)) {
//如果是我们指定的 payloads ,那么就可以指定刷新
TipsBean item = mData.
get
(position);
holder.tvLikeNum.setText(R.id.tv_tips_like_num, item.likes_count >
0
? item.likes_count +
""
:
"Like"
);
holder.tvLikeNum.setTextColor(R.id.tv_tips_like_num, item.likes_count >
0
? CommUtils.getColor(R.color.black) : CommUtils.getColor(R.color.home_item_text_light_gray));
}
else
{
// 如果没有 payloads ,或者不是我们指定的,还是返回默认的整个刷新
super
.onBindViewHolder(holder, position);
如果没有 payload ,当调用
notifyItemChanged
时,RV 会通过回调
onBindViewHolder(holder, position)
来更新当前数据变化的 item ,此时会触发 整个 item 中 view 的重新布局和计算位置,这样的话只要是其中一个 View 状态变化了,最终会导致整个 item 都需要重新布局一遍。
例如上述的例子,在评论的列表中,我只点赞了第一个 item ,我们就通过 payload 来告诉 RV 这个 item 中的 like 文本变化了,那么我们就只需要处理 Like 文本的变化。
Diff的刷新与快速实现方法
每一个 payloads 都要写一次,然后我们在调用
notifyItemChanged
时万一写错了怎么办?有没有一种方式能让程序自动管理,让程序帮我们记录 payloads ?最好还能帮助我们自动排序!
有,我们先看看自动排序的方式:
SortedList 的方式。
SortedList,顾名思义就是排序列表,它适用于列表有序且不重复的场景。并且SortedList会帮助你比较数据的差异,定向刷新数据。而不是简单粗暴的
notifyDataSetChanged
。
例如我们定义一个城市排序的对象:
public
class
City {
private
int
id;
private
String
cityName;
private
String
firstLetter;
我们需要进行排序的规则定义。
public
class
SortedListCallback
extends
SortedListAdapterCallback
<
City
>
{
public
SortedListCallback
(RecyclerView.Adapter adapter)
{
super
(adapter);
* 排序条件
@Override
public
int
compare
(City o1, City o2)
{
return
o1.getFirstLetter.compareTo(o2.getFirstLetter);
* 用来判断两个对象是否是相同的Item。
@Override
public
boolean
areItemsTheSame
(City item1, City item2)
{
return
item1.getId == item2.getId;
* 用来判断两个对象是否是内容的Item。
@Override
public
boolean
areContentsTheSame
(City oldItem, City newItem)
{
if
(oldItem.getId != newItem.getId) {
return
false
;
return
oldItem.getCityName.equals(newItem.getCityName);
再然后在Adapte中使用的时候,不需要Arrylist,要用排序的集合,新的对象SortedList排序集合。
public
class
SortedAdapter
extends
RecyclerView
.
Adapter
<
SortedAdapter
.
ViewHolder
>
{
// 数据源使用SortedList
private
SortedList<City> mSortedList;
private
LayoutInflater mInflater;
public
SortedAdapter
(Context mContext)
{
mInflater = LayoutInflater.from(mContext);
public
void
setSortedList
(SortedList<City> mSortedList)
{
this
.mSortedList = mSortedList;
* 批量更新操作,例如:
* <pre>
* mSortedList.beginBatchedUpdates;
* try {
* mSortedList.add(item1)
* mSortedList.add(item2)
* mSortedList.remove(item3)
* ...
* } finally {
* mSortedList.endBatchedUpdates;
* </pre>
public
void
addData
(List<City> mData)
{
mSortedList.beginBatchedUpdates;
mSortedList.addAll(mData);
mSortedList.endBatchedUpdates;
* 移除item
public
void
removeData
(
int
index)
{
mSortedList.removeItemAt(index);
* 清除集合
public
void
clear
{
mSortedList.clear;
@Override
@NonNull
public
SortedAdapter.
ViewHolder
onCreateViewHolder
(@NonNull ViewGroup parent,
int
viewType)
{
return
new
ViewHolder(mInflater.inflate(R.layout.item_test, parent,
false
));
@Override
public
void
onBindViewHolder
(@NonNull SortedAdapter.ViewHolder holder,
final
int
position)
{
// 。。。
@Override
public
int
getItemCount
{
return
mSortedList.size;
public
class
ViewHolder
extends
RecyclerView
.
ViewHolder
{
public
ViewHolder
(View itemView)
{
super
(itemView);
使用的时候:
RecyclerView mRecyclerView = findViewById(R.id.rv);
mRecyclerView.setLayoutManager(
new
LinearLayoutManager(
this
));
mSortedAdapter =
new
SortedAdapter(
this
);
// SortedList初始化
SortedListCallback mSortedListCallback =
new
SortedListCallback(mSortedAdapter);
SortedList mSortedList =
new
SortedList<>(City.
class
, mSortedListCallback);
mSortedAdapter.setSortedList(mSortedList);
mRecyclerView.setAdapter(mSortedAdapter);
//添加数据
这样确实就能实现自动排序,刷新列表了,相对
notifyDataSetChanged
的暴力刷新,优雅一点。但是它没有 payload 的功能,这个刷新只是刷新的是整个 RV 中的部分Item,但还是刷新整个 item 啊。
有没有办法既能排序又能 payload差异化刷新的方式呢?
肯定有哇, DiffUtil 的方式就此诞生,常用的相关的几个类为 DiffUtil AsyncListDiffer ListAdapter(用于快速实现的封装类)
DiffUtil
能实现排序加payload局部刷新的功能:
当某个 item 的位置变化,触发排序逻辑,有移除和添加的动画。
当某个 item 的位置不变,内容变化,触发 payload 局部刷新。
在子线程中计算DiffResult,在主线程中刷新RecyclerView。
AsyncListDiffer
又是什么东西,为什么需要它。
其实
AsyncListDiffer
就是集成了 AsyncListUtil + DiffUtil 的功能,由于 DiffUtil在计算数据差异
DiffUtil.calculateDiff(mDiffCallback)
是一个耗时操作,需要我们放到子线程去处理,最后在主线程刷新,为了我们开发者更加的方便,谷歌直接提供了
AsyncListDiffer
方便我们直接使用。看来谷歌是怕我们开发者不会使用子线程,直接给我们写好了。
ListAdapter
又是个什么鬼?怎么越来越复杂了?
其实谷歌就喜欢把简单的东西复杂化,如果我们使用
AsyncListDiffer
去实现的话,虽然不用我们操心子线程了,但是还是需要我们定义对象、集合、添加数据的方法 ,如
addNewData
,里面调用
mDiffer.submitList
才能实现。
谷歌还是怕我们不会用吧!直接把
AsyncListDiffer
的使用都给简化了,直接提供了 ListAdapter 包装类,内部对
AsyncListDiffer
的使用做了一系列的封装,使用的时候我们的 RV-Adapter 直接继承 ListAdapter 即可实现
AsyncListDiffer
的功能,内部连设置数据的方法都给我们提供好了。谷歌真的是我们的好爸爸!为我们开发者操碎了心。
那它们都到底
怎么使用
呢?
不管什么方式,我们都需要定义好自己的
DiffUtil.CallBack
,毕竟就算让程序帮我们排序和差分,我们也得告诉程序排序的规则和diff的规则,是吧!
public
class
MyDiffUtilItemCallback
extends
DiffUtil
.
ItemCallback
<
TestBean
>
{
* 是否是同一个对象
@Override
public
boolean areItemsTheSame(
@NonNull
TestBean oldItem,
@NonNull
TestBean newItem) {
return
oldItem.getId == newItem.getId;
* 是否是相同内容
@Override
public
boolean areContentsTheSame(
@NonNull
TestBean oldItem,
@NonNull
TestBean newItem) {
return
oldItem.getName.equals(newItem.getName);
* areItemsTheSame返回true而areContentsTheSame返回false时调用,也就是说两个对象代表的数据是一条,
* 但是内容更新了。此方法为定向刷新使用,可选。
@Nullable
@Override
public
Object getChangePayload(
@NonNull
TestBean oldItem,
@NonNull
TestBean newItem) {
Bundle payload = new Bundle;
if
(!oldItem.getName.equals(newItem.getName)) {
payload.putString(
"KEY_NAME"
, newItem.getName);
if
(payload.size ==
0
){
//如果没有变化 就传空
return
null
;
return
payload;
那个Diff的具体实现我们选用哪一种方案呢?其实三种方式都是可以实现的,这里我们先使用
AsyncListDiffer
的方式来实现。
上面关于
AsyncListDiffer
的介绍我们说过了,虽然不需要我们实现异步操作了,但是我们还是需要实现对象、集合、添加数据的方法等。
示例如下:
public
class
AsyncListDifferAdapter
extends
RecyclerView
.
Adapter
<
AsyncListDifferAdapter
.
ViewHolder
>
{
private
LayoutInflater mInflater;
// 数据的操作由AsyncListDiffer实现
private
AsyncListDiffer<TestBean> mDiffer;
public
AsyncListDifferAdapter
(Context mContext)
{
// 初始化AsyncListDiffe
mDiffer =
new
AsyncListDiffer<>(
this
,
new
MyDiffUtilItemCallback);
mInflater = LayoutInflater.from(mContext);
//添加数据传对象和对象集合都可以
public
void
addData
(TestBean mData)
{
List<TestBean> mList =
new
ArrayList<>;
mList.addAll(mDiffer.getCurrentList);
mList.add(mData);
mDiffer.submitList(mList);
public
void
addData
(List<TestBean> mData)
{
// 由于DiffUtil是对比新旧数据,所以需要创建新的集合来存放新数据。
// 实际情况下,每次都是重新获取的新数据,所以无需这步。
List<TestBean> mList =
new
ArrayList<>;
mList.addAll(mData);
mDiffer.submitList(mList);
//删除数据,要先获取全部集合,再删除指定的集合,再提交删除之后的集合
public
void
removeData
(
int
index)
{
List<TestBean> mList =
new
ArrayList<>;
mList.addAll(mDiffer.getCurrentList);
mList.remove(index);
mDiffer.submitList(mList);
public
void
clear
{
mDiffer.submitList(
null
);
@Override
@NonNull
public
AsyncListDifferAdapter.
ViewHolder
onCreateViewHolder
(@NonNull ViewGroup parent,
int
viewType)
{
return
new
ViewHolder(mInflater.inflate(R.layout.item_test, parent,
false
));
@Override
public
void
onBindViewHolder
(@NonNull ViewHolder holder,
int
position, @NonNull List<Object> payloads)
{
if
(payloads.isEmpty) {
onBindViewHolder(holder, position);
}
else
{
Bundle bundle = (Bundle) payloads.get(
0
);
holder.mTvName.setText(bundle.getString(
"KEY_NAME"
));
@Override
public
void
onBindViewHolder
(@NonNull AsyncListDifferAdapter.ViewHolder holder,
final
int
position)
{
TestBean bean = mDiffer.getCurrentList.get(position);
holder.mTvName.setText(bean.getName);
@Override
public
int
getItemCount
{
return
mDiffer.getCurrentList.size;
static
class
ViewHolder
extends
RecyclerView
.
ViewHolder
{
......
由于
Diff.Callback
我们已经在 Adapter 内部已经初始化了,所以使用的时候我们直接像普通的 RV 设置 Adapter 一样即可。
在更新数据的时候我们使用 Adapter 定义的
addData
和
removeData
即可完成Diff刷新。
使用 ListAdapter 会怎样?
如果觉得使用
AsyncListDiffer
都嫌弃麻烦的话,我们直接使用
ListAdapter
也能实现。
由于我们还是需要一个List集合去保存我们的数据,我们就能对
ListAdapter
再做一个简单的基类封装。
public
abstract
class
BaseRVDifferAdapter
<
T
,
VH
extends
RecyclerView
.
ViewHolder
>
extends
ListAdapter
<
T
,
VH
>
{
protected
Context mContext;
protected
LayoutInflater mInflater;
protected
List<T> mDatas =
new
ArrayList<>;
public
BaseRVDifferAdapter
(Context context, DiffUtil.ItemCallback<T> callback)
{
super
(callback);
mContext = context;
mInflater = LayoutInflater.from(mContext);
//设置数据源
protected
void
setData
(List<T> list)
{
mDatas.clear;
mDatas.addAll(list);
List<T> mList =
new
ArrayList<>;
mList.addAll(mDatas);
submitList(mList);
//添加数据源
protected
void
addData
(List<T> list)
{
mDatas.addAll(list);
List<T> mList =
new
ArrayList<>;
mList.addAll(mDatas);
submitList(mList);
//删除制定索引数据源
protected
void
removeData
(
int
index)
{
mDatas.remove(index);
List<T> mList =
new
ArrayList<>;
mList.addAll(mDatas);
submitList(mList);
//清除全部数据
protected
void
clear
{
mDatas.clear;
submitList(
null
);
//获取adapter维护的数据集合
protected
List<T>
getAdapterData
{
return
mDatas;
我们实现Adater的方法就如下:
public
class
MyListAdapter
extends
BaseRVDifferAdapter
<
TestBean
,
MyListAdapter
.
ViewHolder
>
{
public
MyListAdapter
(Context context)
{
super
(context,
new
MyDiffUtilItemCallback);
@Override
@NonNull
public
MyListAdapter.
ViewHolder
onCreateViewHolder
(@NonNull ViewGroup parent,
int
viewType)
{
return
new
ViewHolder(mInflater.inflate(R.layout.item_test, parent,
false
));
@Override
public
void
onBindViewHolder
(@NonNull ViewHolder holder,
int
position, @NonNull List<Object> payloads)
{
if
(payloads.isEmpty) {
onBindViewHolder(holder, position);
}
else
{
Bundle bundle = (Bundle) payloads.get(
0
);
holder.mTvName.setText(bundle.getString(
"KEY_NAME"
));
@Override
public
void
onBindViewHolder
(@NonNull MyListAdapter.ViewHolder holder,
final
int
position)
{
TestBean bean = getItem(position);
holder.mTvName.setText(bean.getName);
static
class
ViewHolder
extends
RecyclerView
.
ViewHolder
{
TextView mTvName;
ViewHolder(View itemView) {
super
(itemView);
mTvName = itemView.findViewById(R.id.tv_name);
在我们自己的 Adpater 中,我们还是需要初始化我们自己的
Diff.Callback
的。那么在使用的时候也就和普通的RV设置 Adapter 是一样的。
@
Override
protected
void
onCreate
(
Bundle savedInstanceState
) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sorted_list);
RecyclerView mRecyclerView = findViewById(R.id.rv);
mRecyclerView.setLayoutManager(
new
LinearLayoutManager(
this
));
mAsyncListDifferAdapter =
new
AsyncListDifferAdapter(
this
);
mRecyclerView.setAdapter(mAsyncListDifferAdapter);
initData;
private
void
addData
{
List<TestBean> mList =
new
ArrayList;
for
(
int
i =
10
; i <
20
; i++){
mList.
add
(
new
TestBean(i,
"Item "
+ i));
mMyListAdapter.addData(mList);
private
void
initData
{
List<TestBean> mList =
new
ArrayList;
for
(
int
i =
0
; i <
10
; i++){
mList.
add
(
new
TestBean(i,
"Item "
+ i));
mMyListAdapter.setData(mList);
private
void
updateData
{
List<TestBean> mList =
new
ArrayList;
for
(
int
i =
20
; i <
30
; i++){
mList.
add
(
new
TestBean(i,
"Item "
+ i));
mMyListAdapter.addData(mList);
是不是可以无脑使用Diff的方式呢?也不是,当一些普通长列表我们使用默认的RV-Adapter就行了,加载更多,往源数据上面加数据,并不涉及到很多数据源改变的也没必要使用Diff。
那么哪里使用?比如IM的会话列表,长期变动的数据,比如评论区域热评区的数据切换时,比如我们的Android掘金App:
直接暴力的切换数据源然后
notifyDataSetChanged
,这样就会整个页面闪烁。而对于这些特定的一些场景,我们使用 Diff 的功能,就会感觉更加的流畅,体验会好一点哦!
DiffUtil的封装
既然
AsyncListDiffer
和
ListAdapter
都是快速实现的方式,那我们直接使用基本 DiffUtil 行不行?
当然可以,我不想直接使用
ListAdapter
,我就想用
RV.Adapter
,因为我有其他的封装与扩展,我自己会使用异步线程,我不需要你帮我管理,我不需要使用你的
AsyncListDiffer
帮我管理我的 List 对象 。
我们可以直接使用基本的 DiffUtil 。真实使用下来也不是很复杂,只需要做一点点的封装也能很方便的实现逻辑。
先写一个Differ的配置文件,内部可配置主线程,异步线程,和必备的
DiffUtil.Callback
:
class
MyAsyncDifferConfig
<
T
>
(
@SuppressLint(
"SupportAnnotationUsage"
)
@RestrictTo(RestrictTo.Scope.LIBRARY)
val
mainThreadExecutor: Executor?,
val
backgroundThreadExecutor: Executor,
val
diffCallback: DiffUtil.ItemCallback<T>) {
class
Builder
<
T
>
(
private
val
mDiffCallback: DiffUtil.ItemCallback<T>) {
companion
object
{
private
val
sExecutorLock = Any
private
var
sDiffExecutor: Executor? =
null
private
var
mMainThreadExecutor: Executor? =
null
private
var
mBackgroundThreadExecutor: Executor? =
null
fun
setMainThreadExecutor
(executor:
Executor
?)
: Builder<T> {
mMainThreadExecutor = executor
return
this
fun
setBackgroundThreadExecutor
(executor:
Executor
?)
: Builder<T> {
mBackgroundThreadExecutor = executor
return
this
fun
build
: MyAsyncDifferConfig<T> {
if
(mBackgroundThreadExecutor ==
null
) {
synchronized(sExecutorLock) {
if
(sDiffExecutor ==
null
) {
sDiffExecutor = Executors.newSingleThreadExecutor
mBackgroundThreadExecutor = sDiffExecutor
return
MyAsyncDifferConfig(
mMainThreadExecutor,
mBackgroundThreadExecutor!!,
mDiffCallback)
重点是定义一个自己的
AsyncDiffer
,内部使用 DiffUtil 来计算差分。
class
MyAsyncDiffer
<
T
>
(
private
val
adapter: RecyclerView.Adapter<*>,
private
val
config: MyAsyncDifferConfig<T>
private
val
mUpdateCallback: ListUpdateCallback = AdapterListUpdateCallback(adapter)
private
var
mMainThreadExecutor: Executor = config.mainThreadExecutor ?: MainThreadExecutor
private
val
mListeners: MutableList<ListChangeListener<T>> = CopyOnWriteArrayList
private
var
mMaxScheduledGeneration =
0
private
var
mList: List<T>? =
null
private
var
mReadOnlyList = emptyList<T>
private
class
MainThreadExecutor
internal
constructor
: Executor {
val
mHandler = Handler(Looper.getMainLooper)
override
fun
execute
(command:
Runnable
)
{
mHandler.post(command)
fun
getCurrentList
: List<T> {
return
mReadOnlyList
@JvmOverloads
fun
submitList
(newList:
MutableList
<
T
>?, commitCallback:
Runnable
? =
null
)
{
val
runGeneration:
Int
= ++mMaxScheduledGeneration
if
(newList == mList) {
commitCallback?.run
return
val
previousList = mReadOnlyList
if
(newList ==
null
) {
val
countRemoved = mList?.size ?:
0
mList =
null
mReadOnlyList = emptyList
mUpdateCallback.onRemoved(
0
, countRemoved)
onCurrentListChanged(previousList, commitCallback)
return
if
(mList ==
null
) {
mList = newList
mReadOnlyList = Collections.unmodifiableList(newList)
mUpdateCallback.onInserted(
0
, newList.size)
onCurrentListChanged(previousList, commitCallback)
return
val
oldList: List<T> = mList
as
List<T>
config.backgroundThreadExecutor.execute {
val
result = DiffUtil.calculateDiff(
object
: DiffUtil.Callback {
override
fun
getOldListSize
:
Int
{
return
oldList.size
override
fun
getNewListSize
:
Int
{
return
newList.size
override
fun
areItemsTheSame
(oldItemPosition:
Int
, newItemPosition:
Int
)
:
Boolean
{
val
oldItem: T? = oldList[oldItemPosition]
val
newItem: T? = newList[newItemPosition]
return
if
(oldItem !=
null
&& newItem !=
null
) {
config.diffCallback.areItemsTheSame(oldItem, newItem)
}
else
oldItem ==
null
&& newItem ==
null
override
fun
areContentsTheSame
(oldItemPosition:
Int
, newItemPosition:
Int
)
:
Boolean
{
val
oldItem: T? = oldList[oldItemPosition]
val
newItem: T? = newList[newItemPosition]
if
(oldItem !=
null
&& newItem !=
null
) {
return
config.diffCallback.areContentsTheSame(oldItem, newItem)
if
(oldItem ==
null
&& newItem ==
null
) {
return
true
throw
Asserti
override
fun
getChangePayload
(oldItemPosition:
Int
, newItemPosition:
Int
)
: Any? {
val
oldItem: T? = oldList[oldItemPosition]
val
newItem: T? = newList[newItemPosition]
if
(oldItem !=
null
&& newItem !=
null
) {
return
config.diffCallback.getChangePayload(oldItem, newItem)
throw
Asserti
mMainThreadExecutor.execute {
if
(mMaxScheduledGeneration == runGeneration) {
latchList(newList, result, commitCallback)
private
fun
latchList
(
newList:
MutableList
<
T
>,
diffResult:
DiffUtil
.
DiffResult
,
commitCallback:
Runnable
?
val
previousList = mReadOnlyList
mList = newList
mReadOnlyList = Collections.unmodifiableList(newList)
diffResult.dispatchUpdatesTo(mUpdateCallback)
onCurrentListChanged(previousList, commitCallback)
private
fun
onCurrentListChanged
(
previousList:
List
<
T
>,
commitCallback:
Runnable
?
for
(listener
in
mListeners) {
listener.onCurrentListChanged(previousList, mReadOnlyList)
commitCallback?.run
//定义接口
interface
ListChangeListener
<
T
>
{
fun
onCurrentListChanged
(previousList:
List
<
T
>, currentList:
List
<
T
>)
fun
addListListener
(listener:
ListChangeListener
<
T
>)
{
mListeners.add(listener)
fun
removeListListener
(listener:
ListChangeListener
<
T
>)
{
mListeners.remove(listener)
fun
clearAllListListener
{
mListeners.clear
然后我们可以用委托的方式配置,可以让普通的
RecyclerView.Adapter
也能通过配置的方式选择是否使用Differ。
实现我们的控制类接口。
//设置别名简化
typealias IDiffer<T> = IMyDifferController<T>
fun
<T>
differ
: MyDifferController<T> = MyDifferController
interface
IMyDifferController
<
T
>
{
fun
RecyclerView.Adapter
<*>
.
initDiffer
(config:
MyAsyncDifferConfig
<
T
>)
: MyAsyncDiffer<T>
fun
getDiffer
: MyAsyncDiffer<T>?
fun
getCurrentList
: List<T>
fun
setDiffNewData
(list:
MutableList
<
T
>, commitCallback:
Runnable
? =
null
)
fun
addDiffNewData
(list:
MutableList
<
T
>, commitCallback:
Runnable
? =
null
)
fun
addDiffNewData
(t:
T
, commitCallback:
Runnable
? =
null
)
fun
removeDiffData
(index:
Int
)
fun
clearDiffData
fun
RecyclerView.Adapter
<*>
.
onCurrentListChanged
(previousList:
List
<
T
>, currentList:
List
<
T
>)
在对控制类接口实例化,做一些具体的操作逻辑。
class
MyDifferController
<
T
> :
IMyDifferController
<
T
>
{
private
var
mDiffer: MyAsyncDiffer<T>? =
null
override
fun
RecyclerView.Adapter
<*>
.
initDiffer
(config:
MyAsyncDifferConfig
<
T
>)
: MyAsyncDiffer<T> {
mDiffer = MyAsyncDiffer(
this
, config)
val
mListener: MyAsyncDiffer.ListChangeListener<T> =
object
: MyAsyncDiffer.ListChangeListener<T> {
override
fun
onCurrentListChanged
(previousList:
List
<
T
>, currentList:
List
<
T
>)
{
this
@initDiffer
.onCurrentListChanged(previousList, currentList)
mDiffer?.addListListener(mListener)
return
mDiffer!!
override
fun
getDiffer
: MyAsyncDiffer<T>? {
return
mDiffer
override
fun
getCurrentList
: List<T> {
return
mDiffer?.getCurrentList ?: emptyList
override
fun
setDiffNewData
(list:
MutableList
<
T
>, commitCallback:
Runnable
?)
{
mDiffer?.submitList(list, commitCallback)
override
fun
addDiffNewData
(list:
MutableList
<
T
>, commitCallback:
Runnable
?)
{
val
newList = mutableListOf<T>
newList.addAll(mDiffer?.getCurrentList ?: emptyList)
newList.addAll(list)
mDiffer?.submitList(newList, commitCallback)
override
fun
addDiffNewData
(t:
T
, commitCallback:
Runnable
?)
{
val
newList = mutableListOf<T>
newList.addAll(mDiffer?.getCurrentList ?: emptyList)
newList.add(t)
mDiffer?.submitList(newList, commitCallback)
override
fun
removeDiffData
(index:
Int
)
{
val
newList = mutableListOf<T>
newList.addAll(mDiffer?.getCurrentList ?: emptyList)
newList.removeAt(index)
mDiffer?.submitList(newList)
override
fun
clearDiffData
{
mDiffer?.submitList(
null
)
override
fun
RecyclerView.Adapter
<*>
.
onCurrentListChanged
(previousList:
List
<
T
>, currentList:
List
<
T
>)
{
到此我们就能封装一个DiffUtil的工具类了,我们可以选择是否启用Diff,例如我们不使用Diff,我们使用Adapter就是一个普通的Adaper。
class
MyDiffAdapter
: RecyclerView.Adapter<BaseViewHolder> {
private
val
mDatas = arrayListOf<DemoDiffBean>
fun
addData
(list :
List
<
DemoDiffBean
>)
{
mDatas.addAll(list)
notifyDataSetChanged
override
fun
onBindViewHolder
(holder:
BaseViewHolder
, position:
Int
, payloads:
MutableList
<
Any
>)
{
if
(!CheckUtil.isEmpty(payloads) && (payloads[
0
]
as
String) ==
"text"
) {
YYLogUtils.w(
"差分刷新 -------- 文本更新"
)
holder.setText(R.id.tv_job_text, mDatas[position].content)
}
else
{
onBindViewHolder(holder, position)
override
fun
onBindViewHolder
(holder:
BaseViewHolder
, position:
Int
)
{
YYLogUtils.w(
"默认数据赋值 --------"
)
holder.setText(R.id.tv_job_text, mDatas[position].content)
override
fun
onCreateViewHolder
(parent:
ViewGroup
, viewType:
Int
)
: BaseViewHolder {
return
BaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_diff_jobs, parent,
false
))
override
fun
getItemCount
:
Int
{
return
mDatas.size
如果我们想启动Diff的功能的时候,实现这个接口并委托实现即可启用Diff。
class
MyDiffAdapter
: RecyclerView.Adapter<BaseViewHolder>, IDiffer<DemoDiffBean>
by
differ {
init {
initDiffer(MyAsyncDifferConfig.Builder(DiffDemoCallback).build)
override
fun
onBindViewHolder
(holder:
BaseViewHolder
, position:
Int
, payloads:
MutableList
<
Any
>)
{
if
(!CheckUtil.isEmpty(payloads) && (payloads[
0
]
as
String) ==
"text"
) {
YYLogUtils.w(
"差分刷新 -------- 文本更新"
)
holder.setText(R.id.tv_job_text, getCurrentList[position].content)
}
else
{
onBindViewHolder(holder, position)
override
fun
onBindViewHolder
(holder:
BaseViewHolder
, position:
Int
)
{
YYLogUtils.w(
"默认数据赋值 --------"
)
holder.setText(R.id.tv_job_text, getCurrentList[position].content)
override
fun
onCreateViewHolder
(parent:
ViewGroup
, viewType:
Int
)
: BaseViewHolder {
return
BaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_diff_jobs, parent,
false
))
override
fun
getItemCount
:
Int
{
return
getCurrentList.size
使用的时候也是超方便的。
private
fun
initRV
{
mAdapter = MyDiffAdapter
findViewById<RecyclerView>(R.id.recyclerView).vertical.apply {
adapter = mAdapter
divider(Color.BLACK)
private
fun
initData
{
mDatas.clear
for
(i
in
1..10
) {
mDatas.add(DemoDiffBean(i,
"conetnt:
$i
"
))
mAdapter.setDiffNewData(mDatas)
private
fun
initListener
{
findViewById<View>(R.id.diff_1).click {
val
list = mutableListOf<DemoDiffBean>
for
(i
in
1..10
) {
list.add(DemoDiffBean(i,
"Diff1 conetnt:
$i
"
))
mAdapter.setDiffNewData(list)
findViewById<View>(R.id.diff_2).click {
val
list = mutableListOf<DemoDiffBean>
for
(i
in
1..10
) {
list.add(DemoDiffBean(i,
"Diff3 conetnt:
$i
"
))
list.removeAt(
0
)
list.removeAt(
1
)
list.removeAt(
2
)
list[
3
].content =
"自定义乱改的数据"
mAdapter.setDiffNewData(list)
运行的效果如下:
是不是很方便呢?可能有人会问,这么封装有什么好处?
其实这样通过配置的方式,我们可以使用在任意的
RV.Adapter
上面,包括我们自己定义的
BaseAdapter
,
LoadMoreAdapter
等。相对比较灵活吧,方便在原有的效果上快速修改。
当然了,其实关于 Diff 的实现,有这么多种方式可以让大家使用,每一种方案都能实现同样的效果,只是看大家愿不愿意优化而已,每一种方式使用起来都不算难。
关于 paylpoad 和 Diff 的问题,既然 Diff 是在 payload 的基础上实现的,那是不是有 Diff 功能之后我们就不需要手动 payload 了呢?
也不是,上面的介绍中已经讲过了,如果数据频繁的切换,最好是使用Diff,如果就是类似普通的评论列表点赞的效果,我们手动 payload 即可。他们有各自的使用场景。
需要注意的是,如果使用 Diff 要留意对象指针的问题,DiffUtil 首先检查新提交的 newList 与内部持有的 mList 的引用是否相同, 如果相同, 就直接返回。如果不同的引用,才会对 newList 和 mList 做 Diff 算法比较。
可以看的我的 Demo 都是直接另 new 一个 List 来进行操作的,正常开发场景一般我们都是从服务器拿的不同的 List,也不会有问题。而如果从本地拿的数据去比对的时候就需要注意,对比的对象和原有的对象是否是同一个对象,此时可以考虑对象的深拷贝来实现新对象,再拿去和原有对象进行差分对比,关于浅拷贝与深拷贝的对比和如何深拷贝,可以看我之前的
文章
。
好了,本文的全部代码与Demo都已经开源。有兴趣可以看
这里
。项目会持续更新,大家可以关注一下。
https://gitee.com/newki123456/Kotlin-Room
如果感觉本文对你有一点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。
最后推荐一下我做的网站,玩Android:
wanandroid.com
,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
Android 干货分享:插件化换肤原理
framework该这么学,原来系统服务这样设计的?
全网超优雅Android控件可见性检测
扫一扫
关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!
返回搜狐,查看更多
责任编辑: