MVVM之viewmodel和view之间的交互

1,334 阅读2分钟

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很好的解决了这个问题