MVVM中viewmodel是如何将数据变化通知给view的呢?
主要有三种方法:
- 1.databinding -> ObservableArrayList与ObservableField
- 2.LiveDate与SingleLiveEvent
- 3.在view中实例化viewmodel的时候,将view实例传给viewmodel(这种方式用弱引用避免内存泄漏的风险)
对于第一种方法, 以ObservableField为例
- 1.首先,layout.xml中通过databinding关联viewmodel
<layout>
<data>
<variable
name="viewModel"
type="com.fly.tour.news.mvvm.viewmodel.NewsDetailViewModel" />
</data>
<LinearLayout
...
<TextView
...
android:text="@{viewModel.mNewsDetails.content}" />
</LinearLayout>
</layout>
- 2.在实例化viewmodel的时候,将layout与viewmodel绑定
private void initViewModel() {
...
if(mBinding != null){
mBinding.setVariable(viewModelId, mViewModel);
}
...
}
- 3.在viewmodel中创建ObservableField实例
public ObservableField<NewsDetail> mNewsDetails = new ObservableField<>();
- 4.当viewmodel通过model获取数据后, 通过ObservableField.set(data);方法通知data发生变化,这样数据发生变化时,layout也会更新
mModel.getNewsDetailById(id).subscribe(new Observer<RespDTO<NewsDetail>>() {
...
@Override
public void onNext(RespDTO<NewsDetail> newsDetailRespDTO) {
NewsDetail newsDetail = newsDetailRespDTO.data;
if (newsDetail != null) {
mNewsDetails.set(newsDetail);
} else {}
}
...
});
对于ObservableArrayList, 除了上述用法外,往往用于更新列表,该如何实现呢?
这就要用到对于ObservableArrayList的addOnListChangedCallback方法
BaseRefreshViewModel.java
protected ObservableArrayList<T> mList = new ObservableArrayList<>();
- 监听ObservableArrayList的变化,并将列表的Adapter传递给回调ObservableList.OnListChangedCallback
NewsTypeListActivity.java
@Override
public void initView() {
mNewsTypeShowAdapter = new NewsTypeShowBindAdapter(this, mViewModel.getList());
mViewModel.getList().addOnListChangedCallback(ObservableListUtil.getListChangedCallback(mNewsTypeShowAdapter));
mBinding.recview.setAdapter(mNewsTypeShowAdapter);
}
- 当数据变化时,回调函数通过操作adapter.notifyXXX来刷新界面
public static ObservableList.OnListChangedCallback getListChangedCallback(final RecyclerView.Adapter adapter) {
return new ObservableList.OnListChangedCallback() {
@Override
public void onChanged(ObservableList observableList) {
adapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeChanged(i, i1);
}
@Override
public void onItemRangeInserted(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeInserted(i, i1);
}
@Override
public void onItemRangeMoved(ObservableList observableList, int i, int i1, int i2) {
if (i2 == 1) {
adapter.notifyItemMoved(i, i1);
} else {
adapter.notifyDataSetChanged();
}
}
@Override
public void onItemRangeRemoved(ObservableList observableList, int i, int i1) {
adapter.notifyItemRangeRemoved(i, i1);
}
};
}
- 数据是怎么更新到界面的呢?同样的,通过databing
NewsTypeShowBindAdapter.java
@Override
protected void onBindItem(ItemNewsTypeShowBindingBinding binding, final NewsType item, int position) {
...
binding.setNewsType(item);
...
}
layout.xml
<layout>
<data>
<variable
name="newsType"
type="com.fly.tour.api.newstype.entity.NewsType"/>
</data>
<RelativeLayout>
...
<TextView
...
android:text="@={newsType.typename}"
/>
</RelativeLayout>
</layout>
对于LiveDate与SingleLiveEvent, 以SingleLiveEvent为例
- 1.在BaseViewModel中定义SingleLiveEvent:showNetWorkErrViewEvent
BaseViewModel.java
public final class UIChangeLiveData extends SingleLiveEvent {
private SingleLiveEvent<Boolean> showNetWorkErrViewEvent;
...
public SingleLiveEvent<Boolean> getShowInitLoadViewEvent() {
return showInitLoadViewEvent = createLiveData(showInitLoadViewEvent);
}
protected SingleLiveEvent createLiveData(SingleLiveEvent liveData) {
if (liveData == null) {
liveData = new SingleLiveEvent();
}
return liveData;
}
- 2.在BaseMvvmActivity中注册观察者,以便当viewmodel中通知该事件时做出响应 BaseMvvmActivity.java
protected void initBaseViewObservable() {
mViewModel.getUC().getShowNetWorkErrViewEvent().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean show) {
showNetWorkErrView(show);
}
});
}
- 3.NewsDetailViewModel中获取远程数据之前,判断网络断开时发送该事件 NewsDetailViewModel.java
public void getNewsDetailById(final int id) {
if (!NetUtil.checkNetToast()) {
postShowNetWorkErrViewEvent(true);
return;
}
}
public void postShowNetWorkErrViewEvent(boolean show) {
if (mUIChangeLiveData != null) {
mUIChangeLiveData.showNetWorkErrViewEvent.postValue(show);
}
}
SingleLiveEvent.java
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
为什么用SingleLiveEvent而不是直接用MutableLiveData
SingleLiveEvent的特点:
- 它仅仅发送订阅之后出现的更新。
- 它只支持一个观察者。
想想这样一种用MutableLiveData的场景:viewmodel中发现数据更新了,通过setValue通知了订阅者activity,activity观察到更新,Toast提示了该内容,这个时候反转了手机屏幕,activity又注册了观察者,马上又收到了之前viewmodel通知的内容,又弹出了相同的Toast。 之所以重复弹出Toast是因为MutableLiveData会把订阅之前的更新发送给订阅者,SingleLiveEvent很好的解决了这个问题