例子:
下面举一个简单的例子来实践MVVM模式。完整的项目代码下载GitHub上查看: MVVMDemo 例子实现的主要功能是:点击按钮网络查询天气,查询成功后在界面上显示天气信息。主界面如下图所示: MVVM模式的代码组织结构建议按照业务功能进行划分,具体操作是:每个业务功能独立一个包存放,每个业务功能包下面再按Model、View、ViewModel分包存放。所有的Model存放在model包下面,所有的Activity和Fragment存放在activity包下面。所有的ViewModel存放在viewmodel包下面。该例子比较简单,只有一个weather业务功能模块,最终的代码组织结构如下图所示:
编写Model
查询杭州天气的URL为: http://www.weather.com.cn/data/cityinfo/101210101.html 访问该URL将返回串JSON字符串,如下所示:
{
"weatherinfo":{
"city":"杭州",
"cityid":"101210101",
"temp1":"5℃",
"temp2":"20℃",
"weather":"晴转多云",
"img1":"n0.gif",
"img2":"d1.gif",
"ptime":"18:00"
}
}
按照此JSON字符串,可以编写相应的实体类。WeatherData类的代码如下所示:
public class WeatherData {
private WeatherInfo weatherinfo;
public WeatherInfo getWeatherinfo() {
return weatherinfo;
}
public void setWeatherinfo(WeatherInfo weatherinfo) {
this.weatherinfo = weatherinfo;
}
}
WeatherInfo类的代码如下所示:
public class WeatherInfo {
private String city;
private String cityid;
private String temp1;
private String temp2;
private String weather;
private String img1;
private String img2;
private String ptime;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCityid() {
return cityid;
}
public void setCityid(String cityid) {
this.cityid = cityid;
}
public String getTemp1() {
return temp1;
}
public void setTemp1(String temp1) {
this.temp1 = temp1;
}
public String getTemp2() {
return temp2;
}
public void setTemp2(String temp2) {
this.temp2 = temp2;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public String getImg1() {
return img1;
}
public void setImg1(String img1) {
this.img1 = img1;
}
public String getImg2() {
return img2;
}
public void setImg2(String img2) {
this.img2 = img2;
}
public String getPtime() {
return ptime;
}
public void setPtime(String ptime) {
this.ptime = ptime;
}
}
编写ViewModel
ViewModel不涉及任何的视图操作,只进行业务逻辑的处理。通过官方提供的Data Binding库,当ViewModel中的数据发生变化时,UI将自动更新。QueryWeatherViewModel的代码如下所示:
public class QueryWeatherViewModel {
private static final String TAG = "QueryWeatherViewModel";
public final ObservableBoolean loading = new ObservableBoolean(false);
public final ObservableBoolean loadingSuccess = new ObservableBoolean(false);
public final ObservableBoolean loadingFailure = new ObservableBoolean(false);
public final ObservableField<String> city = new ObservableField<>();
public final ObservableField<String> cityId = new ObservableField<>();
public final ObservableField<String> temp1 = new ObservableField<>();
public final ObservableField<String> temp2 = new ObservableField<>();
public final ObservableField<String> weather = new ObservableField<>();
public final ObservableField<String> time = new ObservableField<>();
private Call<WeatherData> mCall;
public QueryWeatherViewModel() {
}
public void queryWeather() {
loading.set(true);
loadingSuccess.set(false);
loadingFailure.set(false);
mCall = RetrofitManager.get()
.create(QueryWeatherRequest.class)
.queryWeather();
mCall.enqueue(new Callback<WeatherData>() {
@Override
public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
WeatherInfo weatherInfo = response.body().getWeatherinfo();
city.set(weatherInfo.getCity());
cityId.set(weatherInfo.getCityid());
temp1.set(weatherInfo.getTemp1());
temp2.set(weatherInfo.getTemp2());
weather.set(weatherInfo.getWeather());
time.set(weatherInfo.getPtime());
loading.set(false);
loadingSuccess.set(true);
}
@Override
public void onFailure(Call<WeatherData> call, Throwable t) {
if (call.isCanceled()) {
Log.i(TAG, "call is canceled.");
} else {
loading.set(false);
loadingFailure.set(true);
}
}
});
}
public void cancelRequest() {
if (mCall != null) {
mCall.cancel();
}
}
}
编写View
View不涉及任何的业务逻辑的处理,只进行界面的显示。在xml布局文件中,通过官方提供的Data Binding库,将UI与ViewModel中的数据进行绑定,当ViewModel中的数据发生变化时,UI将自动更新。xml布局文件的代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.github.cyc.mvvmdemo.weather.viewmodel.QueryWeatherViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/default_content_padding"
tools:context="com.github.cyc.mvvmdemo.weather.activity.QueryWeatherActivity">
<Button
android:id="@+id/btn_query_weather"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/query_weather"
android:enabled="@{viewModel.loading ? false : true}"
android:onClick="@{() -> viewModel.queryWeather()}" />
<RelativeLayout
android:id="@+id/vg_weather_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/btn_query_weather"
android:layout_marginTop="@dimen/query_weather_margin"
android:visibility="@{viewModel.loadingSuccess ? View.VISIBLE : View.GONE}">
<TextView
android:id="@+id/tv_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/city" />
<TextView
android:id="@+id/tv_city_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_city"
android:layout_alignBottom="@id/tv_city"
android:text="@{viewModel.city}"
tools:text="杭州" />
<TextView
android:id="@+id/tv_city_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_city"
android:layout_marginTop="@dimen/query_weather_margin"
android:textStyle="bold"
android:text="@string/city_id" />
<TextView
android:id="@+id/tv_city_id_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_city_id"
android:layout_alignBottom="@id/tv_city_id"
android:text="@{viewModel.cityId}"
tools:text="101210101" />
<TextView
android:id="@+id/tv_temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_city_id"
android:layout_marginTop="@dimen/query_weather_margin"
android:textStyle="bold"
android:text="@string/temperature" />
<TextView
android:id="@+id/tv_temp1_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_temp"
android:layout_alignBottom="@id/tv_temp"
android:text="@{viewModel.temp1}"
tools:text="5℃" />
<TextView
android:id="@+id/tv_tilde"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_temp1_value"
android:layout_alignBottom="@id/tv_temp"
android:text="@string/tilde" />
<TextView
android:id="@+id/tv_temp2_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_tilde"
android:layout_alignBottom="@id/tv_temp"
android:text="@{viewModel.temp2}"
tools:text="10℃" />
<TextView
android:id="@+id/tv_weather"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_temp"
android:layout_marginTop="@dimen/query_weather_margin"
android:textStyle="bold"
android:text="@string/weather" />
<TextView
android:id="@+id/tv_weather_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_weather"
android:layout_alignBottom="@id/tv_weather"
android:text="@{viewModel.weather}"
tools:text="晴" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_weather"
android:layout_marginTop="@dimen/query_weather_margin"
android:textStyle="bold"
android:text="@string/release_time" />
<TextView
android:id="@+id/tv_time_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tv_time"
android:layout_alignBottom="@id/tv_time"
android:text="@{viewModel.time}"
tools:text="10:00" />
</RelativeLayout>
<ProgressBar
android:id="@+id/pb_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />
<TextView
android:id="@+id/tv_query_failure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/query_failure"
android:visibility="@{viewModel.loadingFailure ? View.VISIBLE : View.GONE}" />
</RelativeLayout>
</layout>
在Activity中,通过官方提供的Data Binding库加载布局文件,创建ViewModel,并绑定View和ViewModel。QueryWeatherActivity的代码如下所示:
public class QueryWeatherActivity extends AppCompatActivity {
private QueryWeatherViewModel mViewModel;
private ActivityQueryWeatherBinding mDataBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_query_weather);
mViewModel = new QueryWeatherViewModel();
mDataBinding.setViewModel(mViewModel);
}
@Override
protected void onDestroy() {
super.onDestroy();
mViewModel.cancelRequest();
}
}
总结
MVVM模式将应用分为三层:Model层主要负责数据的提供,View层主要负责界面的显示,ViewModel层主要负责业务逻辑的处理。各个层职责单一,结构清晰,应用可以很方便地进行测试、维护和扩展
|