android MVC && MVP && MVVM分析和对比

一、介绍

  MVC,MVP和MVVM是软件比较常用的三种软件架构,这三种架构的目的都是分离关注,避免将过多的逻辑全部堆积在一个类中,以Android为例,在activity中既有UI的相关处理逻辑,又有数据获取逻辑,从而导致activity逻辑复杂不单一难以维护。为了一个应用可以更好的维护和扩展,我们需要很好的区分相关层级,要不然以后将数据获取方式从数据库变为网络获取时,我们需要去修改整个activity。架构使得view和数据相互独立,我们把应用分成三个不同层级,这样我们就能够单独测试相关层级,使用架构能够把大多数逻辑从activity中移除,方便进行单元测试。

二、MVC

2.1 简单介绍  

Model View Controller模式,MVC将应用分成三个主要层级:Model,View和Controller,它强制将逻辑进行分离,数据结构和Controller逻辑与UI是解耦的,所以测试相关模块变的更简单。 

其实android app的界面开发部分已经是遵从MVC模式的,

  • 视图层(View):主要包括一下View及ViewGroup控件,可以是系统控件也可以是自定义控件。
  • 控制层(Controller):Android的控制层的重任通常落在了众多的Activity的肩上,他们从模型层获取数据,将获取到的数据绑定到view上,并且还需要监听用户的输入等操作。
  • 模型层(Model):对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算,变更等操作也是必须放在的该层的。

MVC模式具体表现在android上的效果如下图所示: 

2.2 实例讲解

https://github.com/MichealPan9999/android_architecture 

以一个获取天气的例子来说,xml布局可视为View层;Activity为Controller层,控制用户输入,将Model层获取到的数据展示到View层;Model层的实体类当然就是用来获取网络数据了。

2.2.1 Model层

Model层处理所有的事件逻辑,最终将结果返回通过controller返回到view层。

1.首先定一个WeatherInfo用于存放天气对象的信息。

public class WeatherInfo {
    public String city;
    public double temp;
    public String WD;
    public String WS;
    public String time;
 //省略 getter 和setter方法

2.定义一个WeatherModel接口实现获取天气的方法

public interface WeatherModel {
    void getWeather(OnLoadWeatherCallback callback);
}

3.定义WeatherModelImpl具体实现WeaterModel接口中的方法

public class WeatherModelImpl implements WeatherModel {
private Context mContext;

public WeatherModelImpl(Context context) {
mContext = context;
}

@Override
public void getWeather(final OnLoadWeatherCallback callback) {//实现获取天气的方法并将加过通过回调返回给Controller

final String url = "http://www.weather.com.cn/data/sk/101010100.html";

if (isNetworkConnected(mContext)) {
new Thread(new Runnable() {
@Override
public void run() {
String netData = null;
netData = getNetWorkData(url);
Log.e("panzqww", "netData = "+netData);
if (!TextUtils.isEmpty(netData)) {
try {
JSONObject jsonObject = new JSONObject(netData);
jsonObject = new JSONObject(jsonObject.getString("weatherinfo"));
WeatherInfo info = new WeatherInfo();
info.setCity(jsonObject.getString("city"));
info.setTemp(Double.parseDouble(jsonObject.getString("temp")));
info.setWD(jsonObject.getString("WD"));
info.setWS(jsonObject.getString("WS"));
info.setTime(jsonObject.getString("time"));
callback.onLoadSuccess(info);//通过回调(监听事件)将info返回到Controller
} catch (JSONException e) {
e.printStackTrace();
} finally {
}
} else {
NetError error = new NetError();
error.errorCode = 405;
error.errorMessage = "获取数据为空!";
callback.onError(error);//通过回调(监听事件)将错误信息返回到Controller
}
}
}).start();


} else {
NetError error = new NetError();
error.errorCode = 404;
error.errorMessage = "网络开小差了,请检查网络";
callback.onError(error);
}
}

private String getNetWorkData(String path) {
String result = null;
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//默认是get请求 如果想使用post必须指明
connection.setRequestMethod("GET");
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);

int code = connection.getResponseCode();
if (code == 200) {
InputStream inputStream = connection.getInputStream();
//把数据流中的json数据 转换成字符串
ByteArrayOutputStream bos = new ByteArrayOutputStream();

int len = -1;
byte[] buff = new byte[1024];
while ((len = inputStream.read(buff)) != -1) {
bos.write(buff, 0, len);
}
result = new String(bos.toByteArray());

}

} catch (IOException e) {
e.printStackTrace();
} finally {
}

return result;
}

public boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
if (mNetworkInfo != null) {
return mNetworkInfo.isAvailable();
}
}
return false;
}
}

 4.定义一个回调接口 OnLoadWeatherCallback 处理获取天气接口返回到controller。

public interface OnLoadWeatherCallback {
    void onLoadSuccess(WeatherInfo info);
    void onError(NetError error);
}

 5. 导入aar文件 

https://blog.csdn.net/lisineng/article/details/70264852

将libcore-release.aar和libcore-ui-release.aar文件拷贝到libs目录下

 

 

在build.gradle中加入

repositories {
    flatDir {
        dirs 'libs'
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:28.+'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    compile(name: 'libcore-release', ext: 'aar')
    compile(name: 'libcore-ui-release', ext: 'aar')
}

 2.2.2 controller 层

实现WeatherActivity实现回调接口OnLoadWeatherCallback

public class WeatherActivity extends Activity implements OnLoadWeatherCallback {

    private TextView tv_name;
    private TextView tv_temperature;
    private TextView tv_wind_d;
    private TextView tv_wind_s;
    private TextView tv_time;
    private LoadingDialog ld;
    private WeatherModel weatherModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weather);
        tv_name = (TextView) findViewById(R.id.tv_name);
        tv_temperature = (TextView) findViewById(R.id.tv_temperature);
        tv_wind_d = (TextView) findViewById(R.id.tv_wind_d);
        tv_wind_s = (TextView) findViewById(R.id.tv_wind_s);
        tv_time = (TextView) findViewById(R.id.tv_time);

        weatherModel = new WeatherModelImpl(this);
        ld = new LoadingDialog(this);
        ld.setLoadingText("正在获取天气...");
        ld.show();
        weatherModel.getWeather(this);
    }
    private void onShowWeather(WeatherInfo weatherInfo){
        tv_name.setText(weatherInfo.getCity());
        tv_temperature.setText(weatherInfo.getTemp()+"");
        tv_wind_d.setText(weatherInfo.getWD());
        tv_wind_s.setText(weatherInfo.getWS());
        tv_time.setText(weatherInfo.getTime());
    }

    @Override
    public void onLoadSuccess(WeatherInfo info) {

        ld.dismiss();
        onShowWeather(info);
    }

    @Override
    public void onError(NetError error) {

        ld.dismiss();
        String errorInfo = error.errorCode+": "+error.errorMessage;
        Log.e("panzqww","errorInfo = "+errorInfo);
        //Toast.makeText(WeatherActivity.this,errorInfo,Toast.LENGTH_SHORT).show();
    }

 三 MVP

3.1 MVP 介绍

在Android项目中,Activity和Fragment占据了大部分的开发工作。如果有一种设计模式(或者说代码结构)专门是为优化Activity和Fragment的代码而产生的,你说这种模式重要不?这就是MVP设计模式。

按照MVC的分层,Activity和Fragment(后面只说Activity)应该属于View层,用于展示UI界面,以及接收用户的输入,此外还要承担一些生命周期的工作。Activity是在Android开发中充当非常重要的角色,特别是TA的生命周期的功能,所以开发的时候我们经常把一些业务逻辑直接写在Activity里面,这非常直观方便,代价就是Activity会越来越臃肿,超过1000行代码是常有的事,而且如果是一些可以通用的业务逻辑(比如用户登录),写在具体的Activity里就意味着这个逻辑不能复用了。如果有进行代码重构经验的人,看到1000+行的类肯定会有所顾虑。因此,Activity不仅承担了View的角色,还承担了一部分的Controller角色,这样一来V和C就耦合在一起了,虽然这样写方便,但是如果业务调整的话,要维护起来就难了,而且在一个臃肿的Activity类查找业务逻辑的代码也会非常蛋疼,所以看起来有必要在Activity中,把View和Controller抽离开来,而这就是MVP模式的工作了。 

MVP模式的核心思想:

MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。

这就是MVP模式,现在这样的话,Activity的工作的简单了,只用来响应生命周期,其他工作都丢到Presenter中去完成。从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大的不同之处。 

3.2 MVP模式的作用

MVP的好处都有啥:

  • 分离了视图逻辑和业务逻辑,降低了耦合

  • Activity只处理生命周期的任务,代码变得更加简洁

  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性

  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试

  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM

Activity 代码变得更加简洁:

相信很多人阅读代码的时候,都是从Activity开始的,对着一个1000+行代码的Activity,看了都觉得难受。

使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。

采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。 

采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。

 

上面一张简单的MVP模式的UML图,从图中可以看出,使用MVP,至少需要经历以下步骤:

  1. 创建IPresenter接口,把所有业务逻辑的接口都放在这里,并创建它的实现PresenterCompl(在这里可以方便地查看业务功能,由于接口可以有多种实现所以也方便写单元测试)

  2. 创建IView接口,把所有视图逻辑的接口都放在这里,其实现类是当前的Activity/Fragment

  3. 由UML图可以看出,Activity里包含了一个IPresenter,而PresenterCompl里又包含了一个IView并且依赖了Model。Activity里只保留对IPresenter的调用,其它工作全部留到PresenterCompl中实现

  4. Model并不是必须有的,但是一定会有View和Presenter 

3.3 实例讲解

3.3.1 首先查看MainActivity代码

public class MainActivity extends Activity implements ILoginView, View.OnClickListener {

    private EditText editUser;
    private EditText editPass;
    private Button btnLogin;
    private Button btnClear;
    ILoginPresenter loginPresenter;//初始化 loginPresenter 处理清除数据,显示进度条,实现登录逻辑
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViews();
        setListener();
        initPersenter();
    }

    private void findViews() {
        //find view
        editUser = (EditText) this.findViewById(R.id.et_login_username);
        editPass = (EditText) this.findViewById(R.id.et_login_password);
        btnLogin = (Button) this.findViewById(R.id.btn_login_login);
        btnClear = (Button) this.findViewById(R.id.btn_login_clear);
        progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
    }

    private void setListener() {
        //set listener
        btnLogin.setOnClickListener(this);
        btnClear.setOnClickListener(this);
    }

    private void initPersenter() {
        //init
        loginPresenter = new LoginPresenterCompl(this);
        loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_login_clear:
                loginPresenter.clear();//LoginPresenterCompl / clear()方法 ->调用 iLoginView.onClearText() -> MainActivity / onClearText()
                break;
            case R.id.btn_login_login:
                loginPresenter.setProgressBarVisiblity(View.VISIBLE);//LoginPresenter / setProgressBarVisiblity() -> 
                  //调用 ILoginView.onSetProgressBarVisibility() -> MainActivity / onSetprogressBarVisibility() btnLogin.setEnabled(
false); btnClear.setEnabled(false); loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());// LoginPresenterComl / doLogin() ->
                 //过五秒钟 调用 iLoginView.onLoginResult() -> MainActivity / onLoginResult()
break; } } @Override public void onClearText() { editUser.setText(""); editPass.setText(""); } @Override public void onLoginResult(Boolean result, int code) { loginPresenter.setProgressBarVisiblity(View.INVISIBLE); btnLogin.setEnabled(true); btnClear.setEnabled(true); if (result){ Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show(); } else Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show(); } @Override public void onSetProgressBarVisibility(int visibility) { progressBar.setVisibility(visibility); } }

3.3.2 model

public interface IUser {
    String getName();

    String getPasswd();

    int checkUserValidity(String name, String passwd);
}
public class UserModel implements IUser {
    String name;
    String passwd;

    public UserModel(String name, String passwd) {
        this.name = name;
        this.passwd = passwd;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getPasswd() {
        return passwd;
    }

    @Override
    public int checkUserValidity(String name, String passwd) {//获取数据,用户名、密码验证功能
        if (TextUtils.isEmpty(name)||TextUtils.isEmpty(passwd)||!name.equals(getName())||!passwd.equals(getPasswd())){
            return -1;
        }
        return 0;
    }
}

3.3.3 view

public interface ILoginView {
    public void onClearText();

    public void onLoginResult(Boolean result, int code);

    public void onSetProgressBarVisibility(int visibility);

}

3.3.4 presenter

public interface ILoginPresenter {
    void clear();

    void doLogin(String name, String passwd);

    void setProgressBarVisiblity(int visiblity);
}
public class LoginPresenterCompl implements ILoginPresenter {
    ILoginView iLoginView;
    IUser user;
    Handler handler;

    public LoginPresenterCompl(ILoginView iLoginView) {
        this.iLoginView = iLoginView;
        initUser();
        handler = new Handler(Looper.getMainLooper());
    }

    private void initUser() {
        user = new UserModel("mvp", "mvp");//实例化UserModel对象
    }

    @Override
    public void clear() {
        iLoginView.onClearText();//清空输入框
    }

    @Override
    public void doLogin(String name, String passwd) {
        Boolean isLoginSuccess = true;
        final int code = user.checkUserValidity(name, passwd);
        if (code != 0) {
            isLoginSuccess = false;
        }
        final Boolean result = isLoginSuccess;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                iLoginView.onLoginResult(result, code);//显示登录结果
            }
        }, 5000);
    }

    @Override
    public void setProgressBarVisiblity(int visiblity) {
        iLoginView.onSetProgressBarVisibility(visiblity);//显示隐藏进度条
    }
}

 四、MVMM

博客参考地址:https://blog.csdn.net/zhouxu88/article/details/78284198

 4.1 MVP 优缺点

在说MVVM之前,简单回顾一下MVP分层,MVP总共分成三层:

a 、View: 视图层,对应xml文件与Activity/Fragment;
b 、Presenter: 逻辑控制层,同时持有View和Model对象;
c 、Model: 实体层,负责获取实体数据。

MVP模式有其很大的优点

1.解耦合,业务逻辑和视图分离;
2.项目代码结构(文件夹)清晰,一看就知道什么类干什么事情;
3.便于单元测试(其实还是第一点);
4.协同工作(例如在设计师没出图之前可以先写一些业务逻辑代码或者其他人接手代码改起来比较容易);

但是也有美中不足的部分,MVP模式的缺点如下:

1.Presente层与View层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。因为View定义的方法并不一定全部要用到,可能只是后面要用到先定义出来(后面要不要删也未知),而且如果后面有些方法要删改,Presenter和Activity都要删改,比较麻烦;

2.V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。

3.复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决,这已经不是接口粒度把控的问题了,一旦业务逻辑越来越多,View定义的方法越来越多,会造成Activity和Fragment实现的方法越来越多,依然臃肿。

4.2 MVVM介绍

1.View层就是展示数据的,以及接收到用户的操作传递给viewModel层,通过dataBinding实现数据与view的单向绑定或双向绑定
2.Model层最重要的作用就是获取数据了,当然不止于此,model层将结果通过接口的形式传递给viewModel层
3.ViewModel 层通过调用model层获取数据,以及业务逻辑的处理。
4.mvvm中 viewModel 和MVP中的presenter 的作用类似 ,只不过是通过 databinding 将数据与ui进行了绑定。 

4.2.1 View

View层做的就是和UI相关的工作,我们只在XML和Activity或Fragment写View层的代码,View层不做和业务相关的事,也就是我们的Activity 不写和业务逻辑相关代码,也不写需要根据业务逻辑来更新UI的代码,因为更新UI通过Binding实现,更新UI在ViewModel里面做(更新绑定的数据源即可),Activity 要做的事就是初始化一些控件(如控件的颜色,添加 RecyclerView 的分割线),Activity可以更新UI,但是更新的UI必须和业务逻辑和数据是没有关系的,只是单纯的根据点击或者滑动等事件更新UI(如 根据滑动颜色渐变、根据点击隐藏等单纯UI逻辑),Activity(View层)是可以处理UI事件,但是处理的只是处理UI自己的事情,View层只处理View层的事。简单的说:View层不做任何业务逻辑、不涉及操作数据、不处理数据、UI和数据严格的分开。

4.2.2 ViewModel

ViewModel层做的事情刚好和View层相反,ViewModel 只做和业务逻辑和业务数据相关的事,不做任何和UI、控件相关的事,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,操作的也都是对数据进行操作,这些个数据源绑定在相应的控件上会自动去更改UI,开发者不需要关心更新UI的事情。DataBinding 框架已经支持双向绑定,这使得我们在可以通过双向绑定获取View层反馈给ViewModel层的数据,并进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件统一化,方便ViewModel对事件的处理和代码的美观。为此我们通过BindingAdapter 对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把可能你需要的数据带给你,这使得我们处理事件的时候也只关心处理数据就行了,具体见MVVM Light Toolkit 使用指南的 Command 部分。再强调一遍ViewModel 不做和UI相关的事。

4.2.3 Model
Model 的职责很简单,基本就是实体模型(Bean)同时包括Retrofit 的Service ,ViewModel 可以根据Model 获取一个Bean的Observable( RxJava ),然后做一些数据转换操作和映射到ViewModel 中的一些字段,最后把这些字段绑定到View层上 

 

可以看到,MVVM模式的最大亮点是双向绑定

单向绑定上,数据的流向是单方面的,只能从代码流向UI;双向绑定的数据流向是双向的,当业务代码中的数据改变时,UI上的数据能够得到刷新;当用户通过UI交互编辑了数据时,数据的变化也能自动的更新到业务代码中的数据上。对于双向绑定,刚好可以使用DataBinding,DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个关键的工具。所以Android中实现MVVM就方便多了,IOS中还要使用block回调,或者使用reactiveCocoa库。

4.3 实例讲解

4.3.1 build.gradle

dataBinding {
        enabled = true
    }
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'com.android.support:appcompat-v7:26.+'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

compile 'io.reactivex.rxjava2:rxjava:2.1.5'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.jcodecraeer:xrecyclerview:1.3.2'
compile 'com.android.support:design:26.1.0'
}
 

 

4.3.2 MainActivity

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        mContext = this;
        initRecyclerView();
        newsVM = new NewsVM(this, newsAdapter);
    }

    private void initRecyclerView() {
        binding.newsRv.setRefreshProgressStyle(ProgressStyle.BallClipRotate); //设置下拉刷新的样式
        binding.newsRv.setLoadingMoreProgressStyle(ProgressStyle.BallClipRotate); //设置上拉加载更多的样式
        binding.newsRv.setArrowImageView(R.mipmap.pull_down_arrow);
        binding.newsRv.setLoadingListener(this);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        binding.newsRv.setLayoutManager(layoutManager);
        newsAdapter = new NewsAdapter(this);
        binding.newsRv.setAdapter(newsAdapter);
    }

    @Override
    public void onRefresh() {
        //下拉刷新
        newsVM.loadRefreshData();
    }

    @Override
    public void onLoadMore() {
        //上拉加载更多
        newsVM.loadMoreData();
    }

    @Override
    public void loadStart(int loadType) {
        if (loadType == FIRST_LOAD) {
            DialogHelper.getInstance().show(mContext, "加载中...");
        }
    }

    @Override
    public void loadComplete() {
        DialogHelper.getInstance().close();
        binding.newsRv.loadMoreComplete(); //结束加载
        binding.newsRv.refreshComplete(); //结束刷新
    }

    @Override
    public void loadFailure(String message) {
        DialogHelper.getInstance().close();
        binding.newsRv.loadMoreComplete(); //结束加载
        binding.newsRv.refreshComplete(); //结束刷新
        ToastUtils.show(mContext, message);
    }

4.3.3 model

获取解析网络数据,并通过接口回调给ViewModel

 
  
List<SimpleNewsBean> simpleNewsBeanList = new ArrayList<SimpleNewsBean>();
public void loadNewsData(final int page, final BaseLoadListener<SimpleNewsBean> loadListener) {
        HttpUtils.getNewsData()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver<NewsBean>() {
                    @Override
                    public void onNext(@NonNull NewsBean newsBean) {
                        Log.i(TAG, "onNext: ");
                        List<NewsBean.OthersBean> othersBeanList = newsBean.getOthers();
                        simpleNewsBeanList.clear();
                        if (othersBeanList != null && othersBeanList.size() > 0) {
                            for (NewsBean.OthersBean othersBean : othersBeanList) {
                                String thumbnail = othersBean.getThumbnail();
                                String name = othersBean.getName();
                                String description = othersBean.getDescription();
                                Log.i(TAG, "thumbnail:---->" + thumbnail);
                                Log.i(TAG, "name:---->" + name);
                                Log.i(TAG, "description:---->" + description);

                                //构造Adapter所需的数据源
                                SimpleNewsBean simpleNewsBean = new SimpleNewsBean();
                                simpleNewsBean.thumbnail.set(thumbnail);
                                simpleNewsBean.name.set(name);
                                simpleNewsBean.description.set(description);
                                simpleNewsBeanList.add(simpleNewsBean);

                                if (page > 1) {
                                    //这个接口暂时没有分页的数据,这里为了模拟分页,通过取第1条数据作为分页的数据
                                    break;
                                }
                            }
                        }
                    }

                    @Override
                    protected void onStart() {
                        super.onStart();
                        Log.i(TAG, "onStart: ");
                        loadListener.loadStart();
                    }

                    @Override
                    public void onError(@NonNull Throwable throwable) {
                        Log.i(TAG, "onError: " + throwable.getMessage());
                        loadListener.loadFailure(throwable.getMessage());
                    }

                    @Override
                    public void onComplete() {
                        Log.i(TAG, "onComplete: ");
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                loadListener.loadSuccess(simpleNewsBeanList);
                                loadListener.loadComplete();
                            }
                        }, 2000);
                    }
                });
    }

4.3.4 ViewModel

  只是做了数据和业务逻辑的处理,并没有任何更新UI的操作,也没有通过binding对象去操作UI,所有的UI都是通过view接口回调到activity去处理。再次强调一下ViewModel中持有的对象是view和mode这两个接口,处理的是业务逻辑,而不应该是databing对象,对ui的具体操作还是应该放在view层。

public class NewsVM implements BaseLoadListener<SimpleNewsBean> {
    private static final String TAG = "NewsVM";
    private INewsModel mNewsModel;
    private INewsView mNewsView;
    private NewsAdapter mAdapter;
    private int currPage = 1; //当前页数
    private int loadType; //加载数据的类型

    public NewsVM(INewsView mNewsView, NewsAdapter mAdapter) {
        this.mNewsView = mNewsView;
        this.mAdapter = mAdapter;
        mNewsModel = new NewsModelImpl();
        getNewsData();
    }

    /**
     * 第一次获取新闻数据
     */
    private void getNewsData() {
        loadType = MainConstant.LoadData.FIRST_LOAD;
        mNewsModel.loadNewsData(currPage, this);
    }

    /**
     * 获取下拉刷新的数据
     */
    public void loadRefreshData() {
        loadType = MainConstant.LoadData.REFRESH;
        currPage = 1;
        mNewsModel.loadNewsData(currPage, this);
    }

    /**
     * 获取上拉加载更多的数据
     */
    public void loadMoreData() {
        loadType = MainConstant.LoadData.LOAD_MORE;
        currPage++;
        mNewsModel.loadNewsData(currPage, this);
    }

    @Override
    public void loadSuccess(List<SimpleNewsBean> list) {
        if (currPage > 1) {
            //上拉加载的数据
            mAdapter.loadMoreData(list);
        } else {
            //第一次加载或者下拉刷新的数据
            mAdapter.refreshData(list);
        }
    }

    @Override
    public void loadFailure(String message) {
        // 加载失败后的提示
        if (currPage > 1) {
            //加载失败需要回到加载之前的页数
            currPage--;
        }
        mNewsView.loadFailure(message);
    }

    @Override
    public void loadStart() {
        mNewsView.loadStart(loadType);
    }

    @Override
    public void loadComplete() {
        mNewsView.loadComplete();
    }
}

4.3.5 Adapter

public class NewsAdapter extends BaseAdapter<SimpleNewsBean, BaseViewHolder> {

    public NewsAdapter(Context context) {
        super(context);
    }

    @Override
    public BaseViewHolder onCreateVH(ViewGroup parent, int viewType) {
        ViewDataBinding dataBinding = DataBindingUtil.inflate(inflater, R.layout.item_news, parent, false);
        return new BaseViewHolder(dataBinding);
    }

    @Override
    public void onBindVH(BaseViewHolder baseViewHolder, int position) {
        ViewDataBinding binding = baseViewHolder.getBinding();
        binding.setVariable(BR.simpleNewsBean, mList.get(position));
        binding.setVariable(BR.position,position);
        binding.setVariable(BR.adapter,this);
        binding.executePendingBindings(); //防止闪烁
    }


    /**
     * 点赞
     *
     * @param simpleNewsBean
     * @param position
     */
    public void clickDianZan(SimpleNewsBean simpleNewsBean, int position) {
        if (simpleNewsBean.isGood.get()) {
            simpleNewsBean.isGood.set(false);
            ToastUtils.show(mContext, "取消点赞 position=" + position);
        } else {
            simpleNewsBean.isGood.set(true);
            ToastUtils.show(mContext, "点赞成功 position=" + position);
        }
    }
}

item_news.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <import type="com.example.mvvm.R" />

        <variable
            name="simpleNewsBean"
            type="com.example.mvvm.bean.SimpleNewsBean" />

        <variable
            name="adapter"
            type="com.example.mvvm.adapter.NewsAdapter" />

        <variable
            name="position"
            type="int" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp">

        <ImageView
            android:id="@+id/header_iv"
            android:layout_width="120dp"
            android:layout_height="60dp"
            app:imageUrl="@{simpleNewsBean.thumbnail}" />

        <!--标题-->
        <TextView
            android:id="@+id/title_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_toEndOf="@id/header_iv"
            android:text="@{simpleNewsBean.name}"
            android:textColor="#000"
            android:textSize="16sp" />

        <!--描述-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignStart="@id/title_tv"
            android:layout_below="@id/title_tv"
            android:layout_marginTop="8dp"
            android:text="@{simpleNewsBean.description}"
            android:textSize="14sp" />

        <!--点赞或者取消点赞,onClick()用的lambda表达式-->
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_below="@id/header_iv"
            android:layout_marginEnd="15dp"
            android:layout_marginTop="8dp"
            android:onClick="@{()->adapter.clickDianZan(simpleNewsBean,position)}"
            app:resId="@{simpleNewsBean.isGood ? R.mipmap.dianzan_press : R.mipmap.dianzan_normal }" />

    </RelativeLayout>
</layout>

注意:

1.这个ImageView的onclick方法是通过lambda表达式来实现的,它的点击事件事件就是adapter的clickDianZan()方法来完成的,里面引入的几个变量都是在adapter中设置的。

2.因为我们没有 获取具体的binding类型,所以我们通过调用setVariable(a,b)来设置。 a代表:通过BR类来查找xml中variable标签中属性name定义的名字 ,b代表:事件或数据。当然,你也可以根据item布局对应的具体的Binding来实现,比如这里就是ItemNewsBinding

 

转载于:https://www.cnblogs.com/qiangge-python/p/9708256.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值