前言
EpoxyRecyclerView很好地支持列表中有多种布局类型,让我们在使用的过程无需去关注ViewType,而是直接为每个类型定义对应的Model就可以了。它会自动为我们做了差分,还提高了的性能。
EpoxyRecyclerView的Github地址:
/airbnb/epoxy
EpoxyRecyclerView的使用
添加依赖:
在app的build.gradle文件中添加
apply plugin: 'kotlin-kapt'
- 1
然后加入依赖:
implementation ':epoxy:3.7.0'
kapt ':epoxy-processor:3.7.0'
- 1
- 2
使用:
1.与使用RecyclerView一样,在布局文件里边加入EpoxyRecyclerView
<LinearLayout
xmlns:android="/apk/res/android"
xmlns:tools="/tools"
xmlns:app="/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<
android:id="@+id/epoxy_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
2.创建一个Controller:
EpoxyController有好几个子类,可根据类型参数的个数来选择继承,以下例子继承的是一个参数的TypedEpoxyController,如果有两个参数的话可继承Typed2EpoxyController,以此类推,最高的数字为4:
class HeaderController() : TypedEpoxyController<List<String>>() {
override fun buildModels(titles: List<String>?) {
}
}
- 1
- 2
- 3
- 4
继承后需要实现buildModels方法,参数为我们展示需要用到的数据。
3.创建Model:
继承EpoxyModelWithHolder,并用注解EpoxyModelClass来标记这是一个Model类,然后会生成对应的类使用。Model类需要是抽象的:
@EpoxyModelClass(layout = R.layout.header_view)
abstract class HeaderModel : EpoxyModelWithHolder<Holder>(){
@EpoxyAttribute
lateinit var title: String
override fun bind(holder: Holder) {
holder.tvHeader.text =title
}
}
class Holder :EpoxyHolder(){
lateinit var tvHeader : TextView
override fun bindView(itemView: View) {
tvHeader = itemView.findViewById(R.id.tv_header)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
layout属性表示对应的布局文件。
写好Model类之后,build一下,然后就可以在controller中添加Model了,这里只用一种Model,在实际开发中可根据对应的条件添加相应的Model:
class HeaderController(private val callback: OriginHeaderModel.OnBtnCallback) : TypedEpoxyController<List<String>>() {
override fun buildModels(titles: List<String>?) {
originHeader {
id("button")
callback(callback)
}
titles?.forEachIndexed { index, title ->
if (index%2==0){
header {
id(title.hashCode())
title(title)
}
}else{
headerFlase {
id(title.hashCode())
falseTitle(title)
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
完成了Controller和Model后,就可以进行使用了:
需要给EpoxyRecyclerView设置Controller,当更新数据时,则直接给controller设置数据即可:
mEpoxyRecyclerView.setController(headerController)
private fun updateController(){
headerController.setData(mTitles)
}
- 1
- 2
- 3
- 4
- 5
EpoxyRecyclerView原理
设置数据
mHomeMessageController.setData(mHomeMessageInfo)
- 1
setData里边会调用requestModelBuild
public void requestModelBuild() {
if (isBuildingModels()) {
throw new IllegalEpoxyUsage("Cannot call `requestModelBuild` from inside `buildModels`");
}
//当在第一次buildModels时,会立即执行,否则的话会延迟执行
//这样是为了尽可能快地将内容显示。
if (hasBuiltModelsEver) {
requestDelayedModelBuild(0);
} else {
buildModelsRunnable.run();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
buildModelsRunnable
//保留核心的代码
private final Runnable buildModelsRunnable = new Runnable() {
@Override
public void run() {
//......
modelsBeingBuilt = new ControllerModelList(getExpectedModelCount());
try {
buildModels();
} catch (Throwable throwable) {
//...
}
runInterceptors();
(modelsBeingBuilt);
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
modelsBeingBuilt是一个数组列表的包装者的子类,用来存放Model,当调用freeze方法后它强制不对列表进行更改。防止拦截器存储列表并去改变它。
buildModels是一个抽象方法,留给我们去实现。
runInterceptors方法会去执行Interceptor的操作。
Adapter的setModels方法:
void setModels(@NonNull ControllerModelList models) {
differ.submitList(models);
}
- 1
- 2
- 3
differ为AsyncEpoxyDiffer对象,用来做差分
public void submitList(@Nullable final List<? extends EpoxyModel<?>> newList) {
final int runGeneration;
@Nullable final List<? extends EpoxyModel<?>> previousList;
synchronized (this) {
// Incrementing generation means any currently-running diffs are discarded when they finish
// We synchronize to guarantee list object and generation number are in sync
runGeneration = generationTracker.incrementAndGetNextScheduled();
previousList = list;
}
//什么事都不用干
if (newList == previousList) {
// nothing to do
onRunCompleted(runGeneration, newList, DiffResult.noOp(previousList));
return;
}
//快速清除
if (newList == null || newList.isEmpty()) {
// fast simple clear all
DiffResult result = null;
if (previousList != null && !previousList.isEmpty()) {
result = DiffResult.clear(previousList);
}
onRunCompleted(runGeneration, null, result);
return;
}
//快速插入
if (previousList == null || previousList.isEmpty()) {
// fast simple first insert
onRunCompleted(runGeneration, newList, DiffResult.inserted(newList));
return;
}
final DiffCallback wrappedCallback = new DiffCallback(previousList, newList, diffCallback);
executor.execute(new Runnable() {
@Override
public void run() {
DiffUtil.DiffResult result = DiffUtil.calculateDiff(wrappedCallback);
onRunCompleted(runGeneration, newList, DiffResult.diff(previousList, newList, result));
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
DiffCallback继承了,几个重要的方法实现如下:
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return diffCallback.areItemsTheSame(
oldList.get(oldItemPosition),
newList.get(newItemPosition)
);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return diffCallback.areContentsTheSame(
oldList.get(oldItemPosition),
newList.get(newItemPosition)
);
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return diffCallback.getChangePayload(
oldList.get(oldItemPosition),
newList.get(newItemPosition)
);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
diffCallback是一个ItemCallback<EpoxyModel<?>>对象,它的实现如下:
private static final ItemCallback<EpoxyModel<?>> ITEM_CALLBACK =
new ItemCallback<EpoxyModel<?>>() {
@Override
public boolean areItemsTheSame(EpoxyModel<?> oldItem, EpoxyModel<?> newItem) {
return oldItem.id() == newItem.id();
}
@Override
public boolean areContentsTheSame(EpoxyModel<?> oldItem, EpoxyModel<?> newItem) {
return oldItem.equals(newItem);
}
@Override
public Object getChangePayload(EpoxyModel<?> oldItem, EpoxyModel<?> newItem) {
return new DiffPayload(oldItem);
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
onRunCompleted方法:
private void onRunCompleted(
final int runGeneration,
@Nullable final List<? extends EpoxyModel<?>> newList,
@Nullable final DiffResult result
) {
// We use an asynchronous handler so that the Runnable can be posted directly back to the main
// thread without waiting on view invalidation synchronization.
MainThreadExecutor.ASYNC_INSTANCE.execute(new Runnable() {
@Override
public void run() {
final boolean dispatchResult = tryLatchList(newList, runGeneration);
if (result != null && dispatchResult) {
resultCallack.onResult(result);
}
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
在主线程调度一个任务,onResult方法:
@Override
public void onResult(@NonNull DiffResult result) {
itemCount = result.newModels.size();
notifyBlocker.allowChanges();
result.dispatchTo(this);
notifyBlocker.blockChanges();
//...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
dispatchTo方法:
public void dispatchTo(Adapter adapter) {
dispatchTo(new AdapterListUpdateCallback(adapter));
}
- 1
- 2
- 3
public void dispatchTo(ListUpdateCallback callback) {
if (differResult != null) {
differResult.dispatchUpdatesTo(callback);
} else if (newModels.isEmpty() && !previousModels.isEmpty()) {
callback.onRemoved(0, previousModels.size());
} else if (!newModels.isEmpty() && previousModels.isEmpty()) {
callback.onInserted(0, newModels.size());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
后面几个流程其实是自动为我们实现差分计算。
Epoxy生成的类,为我们实现了equals和hashCode方法,用于差分时使用。
Model的调用
BaseEpoxyAdapter实现了:
在onCreateViewHolder方法,会创建一个EpoxyViewHolder对象
在onBindViewHolder方法中,会调用EpoxyViewHolder对象的bind方法:
public void bind(@SuppressWarnings("rawtypes") EpoxyModel model,
@Nullable EpoxyModel<?> previouslyBoundModel, List<Object> payloads, int position) {
this.payloads = payloads;
//创建Holder,这在Epoxy生成的代码中有提供创建对应的Holder
if (epoxyHolder == null && model instanceof EpoxyModelWithHolder) {
epoxyHolder = ((EpoxyModelWithHolder) model).createNewHolder();
epoxyHolder.bindView(itemView);
}
//...model的bind前操作
if (previouslyBoundModel != null) {
//提供之前与holder绑定的Model,默认会调用bind(T view方法)
model.bind(objectToBind(), previouslyBoundModel);
} else if (payloads.isEmpty()) {
//将数据绑定给视图
model.bind(objectToBind());
} else {
// 不是全量更新时调用,默认会调用bind(T view方法)
model.bind(objectToBind(), payloads);
}
//...绑定后的操作
epoxyModel = model;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
注意的点
使用同一布局文件:
java.lang.ClassCastException: com.sendi.epoxydemo.Holder cannot be cast to com.sendi.epoxydemo.HeaderFlaseModel$FalseHolder
at com.sendi.epoxydemo.HeaderFlaseModel_.handlePreBind(HeaderFlaseModel_.java:22)
- 1
- 2
BaseEpoxyAdapter的getItemViewType实现寄托给viewTypeManager,
最终会调用getViewType静态方法:
static int getViewType(EpoxyModel<?> model) {
int defaultViewType = model.getViewType();
if (defaultViewType != 0) {
return defaultViewType;
}
Class modelClass = model.getClass();
Integer viewType = VIEW_TYPE_MAP.get(modelClass);
if (viewType == null) {
viewType = -VIEW_TYPE_MAP.size() - 1;
VIEW_TYPE_MAP.put(modelClass, viewType);
}
return viewType;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
首先是使用model的getViewType方法来获取的:
protected int getViewType() {
return getLayout();
}
- 1
- 2
- 3
此方法默认是返回布局id的值,所以当使用同一布局文件时,导致在复用的时候,返回之前的EpoxyViewHolder,它里边持有EpoxyHolder,也就是我们手动实现的Holder,按道理来说,如果不同类型的Model则会重新创建,但由于用相同的布局文件,导致返回发viewType相同,最终复用了不同Model的EpoxyViewHolder,由于调用过bind,所以epoxyHolder不会重新创建,进而导致Holder类型转换错误
结语
EpoxyRecyclerView对于多类型Item的列表,使用起来确实很方便,推荐可以试着使用。本次的分享到这里结束了,有不妥之处欢迎指正或讨论。