如何应对 Android 面试官 -> MVVM 实战一个新闻客户端 (中)

前言


本章我们基于重构的方式进行一个 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 的接口,那么我们就需要进行剥离出来,交给子类实现;

image.png

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值