前言
本章我们基于重构的方式进行一个 MVVM 的实战,我们将一个新闻列表的普通实现,一步一步的改造成 MVVM 的架构模式,一共分为上中下三个章节,本章继续上一章,开始中篇的讲解;
控件化
我们本章向控件化进一步迈进
BaseView 重构
我们上一章,将 TitleView 和 PictureTitleView 抽取了一个 BaseView 来抽取公共的 setData 逻辑,我们还可以继续精进一步;
我们现将我们上一章定义的接口 BaseView 重命名成 IBaseView
public interface IBaseView<DATA extends BaseViewModel> {
void setData(DATA data);
}
然后我们定义一个 IBaseView 的实现类,BaseView
public abstract class BaseView extends LinearLayout implements IBaseView<BaseViewModel> {
public BaseView(Context context) {
super(context);
}
public BaseView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BaseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BaseView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void setData(BaseViewModel baseViewModel) {
}
}
然后我们定义公共的 init 方法,方法中我们进行布局的加载,但是布局 id 我们无法从自身获取,需要通过 工厂方法 模式,提供一个抽象接口来从子类中获取布局 id;同时,通过 泛型 来接收 DataBinding 在 inflater 之后返回的 ViewDataBinding 对象,整体如下:
public abstract class BaseView<VIEW_BINDING extends ViewDataBinding> extends LinearLayout implements IBaseView<BaseViewModel> {
protected VIEW_BINDING mBinding;
private void init() {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = DataBindingUtil.inflate(layoutInflater, getLayoutId(), this, false);
addView(mBinding.getRoot());
}
public abstract int getLayoutId();
}
通常,View 可能会需要一个点击事件,我们这里也提供一下
private void init() {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = DataBindingUtil.inflate(layoutInflater, getLayoutId(), this, false);
mBinding.getRoot().setOnClickListener(this::onRootClick);
addView(mBinding.getRoot());
}
public abstract int getLayoutId();
public abstract void onRootClick(View view);
然后,我们来实现 setData 部分,这里我们将泛型的实现交给 BaseView
public abstract class BaseView<VIEW_BINDING extends ViewDataBinding, DATA extends BaseViewModel> extends LinearLayout implements IBaseView<DATA> {
}
然后,我们发现,当我们在 setData 中去 setViewModel 的时候,报错了,并没有 setViewModel 的接口,那么我们就需要进行剥离出来,交给子类实现;
public abstract class BaseView<VIEW_BINDING extends ViewDataBinding, DATA extends BaseViewModel> extends LinearLayout implements IBaseView<DATA> {
@Override
public void setData(DATA baseViewModel) {
setDataToView(baseViewModel);
mBinding.executePendingBindings();
}
public abstract void setDataToView(DATA data);
}
我们接下来重构 TitleView 和 PictureTitleView,来让它们继承 BaseView
public class TitleView extends BaseView<TitleViewBinding, TitleViewModel> {
public TitleView(Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.title_view;
}
@Override
public void onRootClick(View view) {
}
@Override
public void setDataToView(TitleViewModel titleViewModel) {
mBinding.setTitleViewModel(titleViewModel);
}
}
PictureTitleView
public class PictureTitleView extends BaseView<PictureTitleViewBinding, PictureTitleViewModel> {
public PictureTitleView(Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.picture_title_view;
}
@Override
public void onRootClick(View view) {
}
@Override
public void setDataToView(PictureTitleViewModel pictureTitleViewModel) {
mBinding.setPictureTitleViewModel(pictureTitleViewModel);
}
@BindingAdapter("loadImageUrl")
public static void loadImageUrl(ImageView imageView, String imgUrl) {
Glide.with(imageView.getContext())
.load(imgUrl)
.transition(withCrossFade())
.into(imageView);
}
}
可以看到,我们的 TitleView 和 PictureTitleView 也清爽了很多;到这里,对齐了我们在讲 MVx 的时候的 控件化 的重要性;
Model
我们接下来,终于可以向 MVVM 的架构来迈进了,我们先来看下我们的 Fragment,一开始我们把数据的加载直接放在了 Fragment 中,这其实并不合理,我们需要将数据的获取放到 model 层,我们来进行重构;
首先我们在 base 层定义下我们的 baseModel,创建一个 IBaseModelListener,用来将 model 获取的数据回调到 View
public interface IBaseModelListener<DATA> {
void onLoadSuccess(DATA data);
void onLoadFail(int errorCode, String errorMsg);
}
然后我们定义一个我们用来获取频道列表的 model
public class NewsChannelModel {
private IBaseModelListener<List<NewsChannelsBean.ChannelList>> mListener;
public NewsChannelModel(IBaseModelListener<List<NewsChannelsBean.ChannelList>> mListener) {
this.mListener = mListener;
}
public void load() {
TecentNetworkApi.getService(NewsApiInterface.class)
.getNewsChannels()
.compose(TecentNetworkApi.getInstance().applySchedulers(new BaseObserver<NewsChannelsBean>() {
@Override
public void onSuccess(NewsChannelsBean newsChannelsBean) {
mListener.onLoadSuc