MVVM架构之自动增删改的极简RecycleView的实现
先上个源代码的链接:https://github.com/whenSunSet/MVVMRecycleView
RecycleView是Google替代ListView的一种方案,其有着很高的解耦度,让许多开发者抛弃了以往的ListView,那么RecycleView在MVVM架构下又该怎么实现呢?如何实现单条item刷新以及增减Item的自动刷新呢?今天我就要给大家带来一种方便的高解耦度的解决方案。
1.了解几个工具类
我们先来看几个我制作的工具类,这几个工具类可以一直复用。为啥要介绍他们呢,当然是为了让大家更好的了解内部的机制,在出现坑的时候不至于找不到解决方案,哈哈(当然我使用了这么久还没发现有坑)!
- 1.LayoutManager,这个类是用来提供布局的类型的,大家看看就明白了!
public class LayoutManager {
protected LayoutManager() {
}
public interface LayoutManagerFactory {
RecyclerView.LayoutManager create(RecyclerView recyclerView);
}
public static LayoutManagerFactory linear() {
return new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
return new LinearLayoutManager(recyclerView.getContext());
}
};
}
public static LayoutManagerFactory linear(@Orientation final int orientation, final boolean reverseLayout) {
return new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
return new LinearLayoutManager(recyclerView.getContext(), orientation, reverseLayout);
}
};
}
public static LayoutManagerFactory grid(final int spanCount) {
return new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
return new GridLayoutManager(recyclerView.getContext(), spanCount);
}
};
}
public static LayoutManagerFactory grid(final int spanCount, @Orientation final int orientation, final boolean reverseLayout) {
return new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
return new GridLayoutManager(recyclerView.getContext(), spanCount, orientation, reverseLayout);
}
};
}
public static LayoutManagerFactory staggeredGrid(final int spanCount, @Orientation final int orientation) {
return new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager create(RecyclerView recyclerView) {
return new StaggeredGridLayoutManager(spanCount, orientation);
}
};
}
@IntDef({LinearLayoutManager.HORIZONTAL, LinearLayoutManager.VERTICAL})
@Retention(RetentionPolicy.SOURCE)
public @interface Orientation {
}
}
-
2.ItemViewArg<T>:这个类用于提供每个item界面的xml的id和BR中绑定实例的id
public class ItemViewArg<T> { public static <T> ItemViewArg<T> of(ItemView itemView) { return new ItemViewArg<>(itemView); } public static <T> ItemViewArg<T> of(ItemViewSelector<T> selector) { return new ItemViewArg<>(selector); } private final ItemView itemView; private final ItemViewSelector<T> selector; private ItemViewArg(ItemView itemView) { this.itemView = itemView; this.selector = new ItemViewSelector<T>() { @Override public void select(ItemView itemView, int position, T item) { } @Override public int viewTypeCount() { return 1; } }; } private ItemViewArg(ItemViewSelector<T> selector) { this.itemView = new ItemView(); this.selector = selector; } public void select(int position, T item) { selector.select(itemView, position, item); } public int bindingVariable() { return itemView.bindingVariable(); } public int layoutRes() { return itemView.layoutRes(); } public int viewTypeCount() { return selector.viewTypeCount(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ItemViewArg<?> that = (ItemViewArg<?>) o; if (!itemView.equals(that.itemView)) return false; return selector == that.selector; } @Override public int hashCode() { int result = itemView.hashCode(); result = 31 * result + selector.hashCode(); return result; } public interface ItemViewSelector<T> { void select(ItemView itemView, int position, T item); int viewTypeCount(); } public static class ItemView { public static final int BINDING_VARIABLE_NONE = 0; private int bindingVariable; @LayoutRes private int layoutRes; public static ItemView of(int bindingVariable, @LayoutRes int layoutRes) { return new ItemView().setBindingVariable(bindingVariable).setLayoutRes(layoutRes); } public ItemView set(int bindingVariable, @LayoutRes int layoutRes) { this.bindingVariable = bindingVariable; this.layoutRes = layoutRes; return this; } public ItemView setBindingVariable(int bindingVariable) { this.bindingVariable = bindingVariable; return this; } public ItemView setLayoutRes(@LayoutRes int layoutRes) { this.layoutRes = layoutRes; return this; } public int bindingVariable() { return bindingVariable; } @LayoutRes public int layoutRes() { return layoutRes; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ItemView itemView = (ItemView) o; if (bindingVariable != itemView.bindingVariable) return false; return layoutRes == itemView.layoutRes; } @Override public int hashCode() { int result = bindingVariable; result = 31 * result + layoutRes; return result; } } }
这个类中有一个内部累和一个内部接口,接下来我将来介绍这个类。
- 1.先来看看内部类ItemView:该类有两个int字段,bindingVariable储存实例在BR中生成的id,layoutRes储存所需xml文件的id。其他的几个方法都是为这两个字段服务的,比较简单,大家看看应该就了解了。
- 2.再来看看内部接口ItemViewSelector<T>:我们前面的内部类,只能提供一个布局给item,如果我的item有许多个布局怎么办的,这个接口就是用来解决这个问题。
- 1.select(ItemView itemView, int position, T item):这个方法是开发者自己实现的,item这个字段是咱么列表Item的ViewModel,里面储存着到底当前列表item的布局type。所以我们只要用判断item中布局的种类,然后调用itemView的of或者set方法,就能对列表item的布局进行选择。**其实总的说就是根据字段item中的参数来构造出不同的ItemView**
- 2.viewTypeCount():这个方法很简单,就是返回一下列表item所有布局的种类。
- 3.最后看看ItemViewArg<T>:该类中储存着ItemView和ItemViewSelector<T>的实体,我们在开发的时候只要传入一个实例另一个会自动生成。
- 这其中最重要的一个方法就是select(int position, T item),大家先记着这个方法,在后面我们整个流程串起来了之后,会讲到这个方法。
-
3.BindingAdapters:用过databinding的人都知道,如果我想定义一个xml中能使用的字段,就得构造一个方法,这个类就是RecycleView在xml中字段设置的构造字段的方法。
public class BindingAdapters { @SuppressWarnings("unchecked") @BindingAdapter(value = {"itemView", "items", "itemIds","itemAnimator","itemDecor"}, requireAll = false) public static <T> void setAdapter(final RecyclerView recyclerView, ItemViewArg<T> arg, final List<T> items, BindingRecyclerViewAdapter.ItemIds<T> itemIds,RecyclerView.ItemAnimator animator,RecyclerView.ItemDecoration decor) { if (arg == null) { throw new IllegalArgumentException("itemView must not be null"); } BindingRecyclerViewAdapter<T> adapter = new BindingRecyclerViewAdapter<>(arg); if (items!=null)adapter.setItems(items); if (itemIds!=null)adapter.setItemIds(itemIds); if (animator!=null)recyclerView.setItemAnimator(animator); if (decor!=null)recyclerView.addItemDecoration(decor); recyclerView.setAdapter(adapter); } @BindingAdapter("layoutManager") public static void setLayoutManager(RecyclerView recyclerView, LayoutManager.LayoutManagerFactory layoutManagerFactory) { recyclerView.setLayoutManager(layoutManagerFactory.create(recyclerView)); } @BindingConversion public static ItemViewArg toItemViewArg(ItemViewArg.ItemView itemView) { return ItemViewArg.of(itemView); } @BindingConversion public static ItemViewArg toItemViewArg(ItemViewArg.ItemViewSelector<?> selector) { return ItemViewArg.of(selector); } }
让我来解释一下这几个方法:
- 1.setAdapter():这一个方法中arg是在xml中必须设置的字段,其他都是可选字段,
- 1.arg我们在前面介绍过是为item提供界面id的实例。
- 2.items是为列表提供数据的实例。
- 3.animator是为item的增加删除提供动画的实例。
- 4.decor是为列表提供分割线的实例。
- 5.当然大家还可以为RecycleView设置更多的初始化的实例。
- 2.setLayoutManager():我们前面提供了LayoutManager这个类来生成各种列表排布情况,这方法xml中的字段的构造字段函数。
- 3.两个toItemViewArg()是转换器,能够将ItemView和ItemViewSelector<>转换成ItemViewArg(),这样在xml中itemView字段中也可以填写ItemView和ItemViewSelector<>类型的实例了。
- 4.BindingRecyclerViewAdapter<T>:这个类就是我们平时写RecycleView时要写的adapter,有了这个类之后,大家基本上不用碰adapter了,所有逻辑都能在item的ViewModel中进行。我们就来好好看看这个类。
public class BindingRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final Object DATA_INVALIDATION = new Object();
@NonNull
private final ItemViewArg<T> itemViewArg;//item界面的layout,通过databing设置
private final WeakReferenceOnListChangedCallback<T> callback = new WeakReferenceOnListChangedCallback<>(this);//数据list变化的时候的回调,设置在数据list中,如果list是ObservableList
private List<T> items;//数据的list
private LayoutInflater inflater;//初始化item 界面;
// Currently attached recyclerview, we don't have to listen to notifications if null.
@Nullable
private RecyclerView recyclerView;
public BindingRecyclerViewAdapter(@NonNull ItemViewArg<T> arg) {
this.itemViewArg = arg;
}
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int layoutId) {
if (inflater == null) {
inflater = LayoutInflater.from(viewGroup.getContext());
}
ViewDataBinding binding = DataBindingUtil.inflate(inflater, layoutId, viewGroup, false);
final RecyclerView.ViewHolder holder=new BindingViewHolder(binding);
binding.addOnRebindCallback(new OnRebindCallback() {
@Override
public boolean onPreBind(ViewDataBinding binding) {
return recyclerView != null && recyclerView.isComputingLayout();
}
@Override
public void onCanceled(ViewDataBinding binding) {
if (recyclerView == null || recyclerView.isComputingLayout()) {
return;
}
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
notifyItemChanged(position, DATA_INVALIDATION);
}
}
});
return holder;
}
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
T item = items.get(position);
ViewDataBinding binding = DataBindingUtil.getBinding(viewHolder.itemView);
if (itemViewArg.bindingVariable() != ItemViewArg.ItemView.BINDING_VARIABLE_NONE) {
boolean result = binding.setVariable(itemViewArg.bindingVariable(), item);
if (!result) {
Utils.throwMissingVariable(binding, itemViewArg.bindingVariable(), itemViewArg.layoutRes());
}
binding.executePendingBindings();
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
if (isForDataBinding(payloads)) {
ViewDataBinding binding = DataBindingUtil.getBinding(holder.itemView);
binding.executePendingBindings();
} else {
super.onBindViewHolder(holder, position, payloads);
}
}
public void setItems(@Nullable List<T> items) {
if (recyclerView != null) {
if (this.items instanceof ObservableList) {
((ObservableList<T>) this.items).removeOnListChangedCallback(callback);
}
if (items instanceof ObservableList) {
((ObservableList<T>) items).addOnListChangedCallback(callback);
}
}
this.items = items;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return items == null ? 0 : items.size();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
if (this.recyclerView == null && items != null && items instanceof ObservableList) {
((ObservableList<T>) items).addOnListChangedCallback(callback);
}
this.recyclerView = recyclerView;
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
if (this.recyclerView != null && items != null && items instanceof ObservableList) {
((ObservableList<T>) items).removeOnListChangedCallback(callback);
}
this.recyclerView = null;
}
@Override
public int getItemViewType(int position) {
itemViewArg.select(position, items.get(position));
return itemViewArg.layoutRes();
}
@Override
public long getItemId(int position) {
return itemViewArg.layoutRes();
}
private boolean isForDataBinding(List<Object> payloads) {
if (payloads == null || payloads.size() == 0) {
return false;
}
for (int i = 0; i < payloads.size(); i++) {
Object obj = payloads.get(i);
if (obj != DATA_INVALIDATION) {
return false;
}
}
return true;
}
private static class BindingViewHolder extends RecyclerView.ViewHolder {
public BindingViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
}
}
private static class WeakReferenceOnListChangedCallback<T> extends ObservableList.OnListChangedCallback<ObservableList<T>> {
final WeakReference<BindingRecyclerViewAdapter<T>> adapterRef;
WeakReferenceOnListChangedCallback(BindingRecyclerViewAdapter<T> adapter) {
this.adapterRef = new WeakReference<>(adapter);
}
@Override
public void onChanged(ObservableList sender) {
BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
if (adapter == null) {
return;
}
Utils.ensureChangeOnMainThread();
adapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList sender, final int positionStart, final int itemCount) {
BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
if (adapter == null) {
return;
}
Utils.ensureChangeOnMainThread();
adapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(ObservableList sender, final int positionStart, final int itemCount) {
BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
if (adapter == null) {
return;
}
Utils.ensureChangeOnMainThread();
adapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(ObservableList sender, final int fromPosition, final int toPosition, final int itemCount) {
BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
if (adapter == null) {
return;
}
Utils.ensureChangeOnMainThread();
for (int i = 0; i < itemCount; i++) {
adapter.notifyItemMoved(fromPosition + i, toPosition + i);
}
}
@Override
public void onItemRangeRemoved(ObservableList sender, final int positionStart, final int itemCount) {
BindingRecyclerViewAdapter<T> adapter = adapterRef.get();
if (adapter == null) {
return;
}
Utils.ensureChangeOnMainThread();
adapter.notifyItemRangeRemoved(positionStart, itemCount);
}
}
}
这个类中由两个内部类和其自己组成
- 1.内部类BindingViewHolder,该类集成RecyclerView.ViewHolder只是提供一个ViewHolder比较简单。
- 2.WeakReferenceOnListChangedCallback<T>,这个类是一个弱引用的List监听器,在adapter中我们会传入一个List实体作为数据源,用过databinding的同学都知道,有个ObsrvableList。所以当我们传入的List是这个类型的时候,adapter就会将这个监听器传入ObsrvableList中,一旦我们对数据源进行增删,那么该监听器的相应方法就会被调用,刷新我们的列表。我们可以看见其中的几个方法,onItemRangeChanged、onItemRangeInserted、onItemRangeMoved、onItemRangeRemoved。中调用了adapter的几个对应的刷新的方法。
- 3.最后看看adapter:
- 1.先来看看其内部的实例:
- ItemViewArg<T> itemViewArg:这个类我们之前介绍过,用来提供item的界面参数,在创建adapter的时候传入。
- 2.WeakReferenceOnListChangedCallback<T> callback:这个就是我们之前说的数据源的监听器,当数据源发生更变,就会调用adapter的相应方法对界面进行刷新操作。
- 3.List<T> items:这个就是数据源,一般我们都是传一个ObservableList或者其子类。这样能保证数据源增删的时候自动刷新界面。
- LayoutInflater inflater:用来初始化xml文件的,我想大家都知道。
- 5.RecyclerView recyclerView:这个就不用说了。
- 2.我们再来看看其中的方法,我们按照顺序来:
- 1.onCreateViewHolder(ViewGroup viewGroup, int layoutId)
这个函数是RecycleView初始化每个item时调用的第一个函数,这个函数做了如下几件事。- 1.1到3行初始化了inflater。
- 2.4行创建了该item的ViewDataBinding。
- 3.5行创建了该item的viewHolder,通过前面的解释我们可以知道该viewHoder只是储存了ViewDataBinding的rootView。
- 4.接下来的代码都是为databinding的绑定流程设置监听器,每次绑定之前和取消的时候都会分别调用监听器中的onPreBind(ViewDataBinding binding)和onCanceled(ViewDataBinding binding)。关于这里的具体流程大家可以看看我前一篇解析databinding源码的博客
- 2.接下来会执行item绑定ViewHolder的方法,也就是onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position),我们可以看到该方法有一个重载方法。我们一一解释一下。
- 1.onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads)
- 1.可以看见该方法有if选择语句,如果为true则代表之前该item已经经过初始化过了,所以代码中只需要去寻找到相应的ViewDataBinding,然后执行其绑定操作。
- 2.如果为false,那么该item没有被初始化过,那么就会调用父类方法,而父类中就是调用另一个重载方法,这个方法我们接下来要讲。
- 2.onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
- 1.1行获取了数据。
- 2.2行找到了我们在onCreateViewHolder()创建的ViewDataBinding。
- 3.之后的逻辑就是为ViewDataBinding设置数据。
- 1.onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads)
- 3.其实上面两个方法就是RecycleView初始化一个item的全部流程,接下来我们来讲讲其他辅助的方法。
- setItems(@Nullable List<T> items)
这个方法是我们在BindingAdapters中初始化Adapter的时候用的,通过代码大家可以看出,其实就是判断设置的List数据源的类型是否是ObservableList,如果是的话就设置我们前面说到的监听器,设置了之后我们的RecycleView就能自动增加和删除了。
- setItems(@Nullable List<T> items)
- 2.getItemViewType(int position)
- 1.这个函数是为onCreateViewHolder()方法提供layoutId的函数
- 2.通过我们之前对于ItemViewArg的介绍,大家知道这里最后调用的是ItemViewSelector的select()方法
- 3.如果当初我们构建ItemViewArg传入的是ItemView,那么这里就会返回ItemView中储存的layoutId
- 4.如果当初构建的时候是传入开发者自己实现的ItemViewSelector那么返回的就是开发者自己返回的ItemView实例。
- 1.onCreateViewHolder(ViewGroup viewGroup, int layoutId)
- 1.先来看看其内部的实例:
2.在MVVM下构造极简的RecycleView
我们上面已经把我们接下来要使用的工具类介绍完了,以上的工具类之后基本不需要变化代码,所以在去除工具类之后我们的自动增删改的RecycleView将会只剩业务代码。
在讲解具体代码之前,我提几点我直接对于MVVM这个架构的看法,方便大家了解我之后的代码。
- 1.Activity是View层,负责实现关于界面的操作。关于界面的操作主要有两个方面,一个是View的动画,一个是对于界面上View事件的监听,和数据的变化。
- 1.实现view的动画:绝大部分动画都需要使用到view的引用,所以为了获取view的引用,开发者可以在xml中为view设置id,然后再viewDatabinding中获取该view的引用。
- 2.view事件的监听和界面上数据的变化,比如说TextView、Button、EditText等。一般的方法是在Activity中设置这些view的监听器,然后通过variable这个标签在xml中引入。但是此时就出现了一个问题,那就是一旦监听器多了,就会出现大量的模版代码在xml和Activity文件之中。所以为了解决这个问题我引入了一个UiViewModel的概念:在Activity中实现一个内部类,在该监听器的内部实现所有的监听器,最后在Activity中把UiViewModel引入到xml文件中。
- 2.ViewModel中到底需不需要传入Context,我认为是需要的,原因有一下两点:
- 1.很多情况下在ViewModel中直接使用Activity比兜转半天回到Activity中调用方法,直观许多逻辑也清晰许多,我就试过ViewModel中不使用Context,最后发现Activity和ViewModel中多了许多奇怪的代码,这个我会在之后的MVVM架构中深入讲解。
- 2.其实不在ViewModel中传入Activity主要就是为了解耦,一旦转化成为这个问题那么就很好解决了。我们可以构造一个BaseActivity,然后所有的ViewModel中都只传入BaseAcvitity,而在ViewModel中95%的需要使用Activity的操作,都是不需要了解Activity的实际类型的,所以这就很好的解决了这个问题。
好了废话不多讲了让我们来看看逻辑的代码吧。
-
1.xml的代码
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data class="com.example.administrator.mvvmrecycleview.ActivityMainBinding"> <variable name="viewModel" type="com.example.administrator.mvvmrecycleview.MainActivityViewModel"/> <variable name="uiViewModel" type="com.example.administrator.mvvmrecycleview.MainActivity.MainActivityUIViewModel"/> <import type="com.example.administrator.mvvmrecycleview.recycleViewUtil.LayoutManager"/> <import type="android.view.View"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:onClick="@{uiViewModel.add}" android:text="增加" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content"/> <Button android:onClick="@{uiViewModel.delete}" android:text="删除" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content"/> <Button android:onClick="@{uiViewModel.change}" android:text="修改" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content"/> </LinearLayout> <EditText android:onTextChanged="@{uiViewModel.position}" android:inputType="number" android:hint="输入要改变的item的position" android:layout_width="match_parent" android:layout_height="wrap_content"/> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/my_recycle_view" bind:layoutManager="@{LayoutManager.linear()}" bind:itemView="@{viewModel.mViewSelector}" bind:items="@{viewModel.dataItems}"> </android.support.v7.widget.RecyclerView> </LinearLayout> </layout>
上面的代码很简单主要有以下几点。
- 1.使用variable标签引入了ViewModel和UiViewModel这两个实例。
- 2.设置了3个Button和1个EditText。
- 3.在RecycleView中使用了我在工具类BindingAdapters中定义的items和itemView这两个字段来为RecycleView设置初始化变量。
-
2.再来看看MainActivity中的代码
public class MainActivity extends AppCompatActivity { private com.example.administrator.mvvmrecycleview.ActivityMainBinding mActivityMainBinding; private MainActivityViewModel mMainActivityViewModel; private MainActivityUIViewModel mMainActivityUIViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); mMainActivityViewModel = new MainActivityViewModel(this); mMainActivityUIViewModel = new MainActivityUIViewModel(this); mActivityMainBinding.setViewModel(mMainActivityViewModel); mActivityMainBinding.setUiViewModel(mMainActivityUIViewModel); } public static class MainActivityUIViewModel { private MainActivity mMainActivity; private MainActivityViewModel mMainActivityViewModel; public MainActivityUIViewModel(MainActivity mMainActivity) { this.mMainActivity = mMainActivity; this.mMainActivityViewModel=mMainActivity.mMainActivityViewModel; } public TextViewBindingAdapter.OnTextChanged position=new TextViewBindingAdapter.OnTextChanged() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (!TextUtils.isEmpty(s.toString()))mMainActivityViewModel.positionStr=s.toString(); } }; public View.OnClickListener add=new View.OnClickListener() { @Override public void onClick(View v) { mMainActivityViewModel.add(); } }; public View.OnClickListener delete=new View.OnClickListener() { @Override public void onClick(View v) { mMainActivityViewModel.delete(); } }; public View.OnClickListener change=new View.OnClickListener() { @Override public void onClick(View v) { mMainActivityViewModel.change(); } }; } }
MainActivity中的代码也很简单
- 1.在Activity中获取ViewDatabinding并设置ViewModel和UiViewModel。
- 2.在内部类UiViewModel中,设置了各种监听器。
- 3.值得注意的是:虽然我在UiViewModel中设置了监听器,但是如果涉及到数据存储以及业务逻辑的话,还是需要在ViewModel中进行的,所以我在UiViewModel中传入了具体的MainActivity的引用,由于UiViewModel只是Activity中监听器的集合,并不是真正的ViewModel,所以这里并不违反我之前说的观点。还有就是虽然这样的写法看起来有点绕,但是这样所带来的代码条理性更好。
-
3.再来看看我们最重要的一个类ViewModel,这个类是集合了我们对于一个RecycleView业务逻辑的类,比较重要,但是经过我们前面工具类的封装变得很清晰。
public class MainActivityViewModel { public MainActivity baseActivity; public ItemViewArg.ItemViewSelector<MainActivityItemViewModel> mViewSelector; public final ObservableList<MainActivityItemViewModel> dataItems= new ObservableArrayList<>(); public String positionStr; public MainActivityViewModel(MainActivity b) { baseActivity=b; mViewSelector=new ItemViewArg.ItemViewSelector<MainActivityItemViewModel>() { @Override public void select(ItemViewArg.ItemView itemView, int position, MainActivityItemViewModel item) { itemView.set(com.example.administrator.mvvmrecycleview.BR.viewModel,item.type==1?R.layout.item_one:R.layout.item_two); } @Override public int viewTypeCount() { return 2; } }; for (int i = 0; i < 20; i++) { if (i%2==1){ dataItems.add(new MainActivityItemViewModel(b,1,String .valueOf(i))); }else { dataItems.add(new MainActivityItemViewModel(b,2,String .valueOf(i))); } } } public void add(){ if (TextUtils.isEmpty(positionStr)||dataItems.size()<Integer.parseInt(positionStr))return; dataItems.add(Integer.parseInt(positionStr),new MainActivityItemViewModel(baseActivity,1,"增加")); } public void delete(){ if (TextUtils.isEmpty(positionStr)||dataItems.size()<=Integer.parseInt(positionStr))return; dataItems.remove(Integer.parseInt(positionStr)); } public void change(){ if (TextUtils.isEmpty(positionStr)||dataItems.size()<=Integer.parseInt(positionStr))return; MainActivityItemViewModel mainActivityItemViewModel = dataItems.get(Integer.parseInt(positionStr)); mainActivityItemViewModel.text.set("修改"); } public static class MainActivityItemViewModel{ public MainActivity mBaseActivity; public int type; public final ObservableField<String> text=new ObservableField<>(); public MainActivityItemViewModel(MainActivity baseActivity, int type, String text) { mBaseActivity = baseActivity; this.type = type; this.text.set(text); } public View.OnClickListener click=new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(mBaseActivity, ("你点击了"+text.get()), Toast.LENGTH_SHORT).show(); } }; } }
RecycleView的ViewModel由其自身和一个内部类ItemViewModel构成,ItemViewModel不必多说,就是RecycleView中每个item的ViewModel,值得注意的是,当一个RecycleView的内容比较多的时候,我们可以拆分ViewModel,即将ItemViewModel独立成为一个新类。这个操作在一项目的后期操作起来也很简单,不过一般的项目大部分的逻辑都在ItemViewModel中,所以我才使用了内部类的方式。
- 1.该ViewModel中有几个成员变量。
- 1.MainActivity:这个在大家的项目中推荐像我前面说的那样传入BaseActivity,进行解耦。
- 2.ItemViewArg.ItemViewSelector< MainActivityItemViewModel > :这个接口是我前面介绍的工具类,其内部选择布局的方法需要开发者自己实现,**我们可以看见这个接口的泛型就是ItemViewModel**
- 3.ObservableList< MainActivityItemViewModel >:这个就是我们的数据源,其泛型也是ItemViewModel。
- 2.可以看见我在ViewModel的构造函数中初始化了ItemViewArg.ItemViewSelector和ObservableList。
- 1.ItemViewArg.ItemViewSelector有两个需要被重写的方法
- 1.select(ItemViewArg.ItemView itemView, int position, MainActivityItemViewModel item):**一般情况下我们会在ItemViewModel中放置该item到底使用哪个界面的tag**,所以在这里我们就可以使用item这个参数来选择layoutId。
- 2.viewTypeCount():这个方法就是返回目前到底有几种布局。
- 2.我使用静态数据来初始化ObservableList,在这里大家在项目中会使用网络的数据。然后根据网络的数据来构造ItemViewModel。
- 3.后面有几个方法分别是add,delete和update。这几个方法是在UiViewModel中的监听器中调用的 ,我们可以看见我只是根据position增删改了ObservableList中的数据。
- 4.最后来看看ItemViewModel中的代码,可以看见里面的代码也很简单
- 1.type:用来判断item到底是哪种布局。
- 2.ObservableField< String >:item的数据。
以上就是极简RecycleView自动增删改的全部代码,可以说除了工具类之外几乎所有的代码都是业务逻辑,实现起来也非常简单,大家如果不想了解具体细节可以直接将几个工具类复制到自己项目中就能使用。 这是MVVM架构前奏的第二波,大家可以看看我之前发的databinding源码解析,满满的相信干货大家一定会喜欢。