Android官方架构组件ViewModel+LiveData+DataBinding架构属于自己的MVVM
Android官方架构组件ViewModel+LiveData+DataBinding架构属于自己的MVVM
Demo运行效果
获取Bing每日一图并显示
项目结构
实现过程
1. 添加Glide、Retrofit、RxJava的依赖
implementation 'com.squareup.retrofit2:retrofit:2.4.0'compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'compile 'com.squareup.retrofit2:converter-gson:2.4.0'implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'implementation 'io.reactivex.rxjava2:rxjava:2.1.12'implementation 'com.github.bumptech.glide:glide:4.6.1'annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1'
2. 启用DataBinding
dataBinding{ enabled=true}
3. 添加网络访问权限
4. 解析网络接口返回信息
接口地址:https://cn.bing.com/HPImageArchive.aspx?format=js&idx=1&n=1
{
"images": [
{
"startdate": "20180408",
"fullstartdate": "201804081600",
"enddate": "20180409",
"url": "/az/hprichbg/rb/LenaDelta_ZH-CN9073097502_1920x1080.jpg",
"urlbase": "/az/hprichbg/rb/LenaDelta_ZH-CN9073097502",
"copyright": "位于西伯利亚的勒拿河三角洲野生动物保护区,俄罗斯 (© USGS EROS Data Center/NASA)",
"copyrightlink": "http://www.bing.com/search?q=%E5%8B%92%E6%8B%BF%E6%B2%B3%E4%B8%89%E8%A7%92%E6%B4%B2%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E4%BF%9D%E6%8A%A4%E5%8C%BA&form=hpcapt&mkt=zh-cn",
"quiz": "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20180408_LenaDelta%22&FORM=HPQUIZ",
"wp": true,
"hsh": "b3ed2f27f31a4e68da602e232fe223f0",
"drk": 1,
"top": 1,
"bot": 1,
"hs": []
}
],
"tooltips": {
"loading": "正在加载...",
"previous": "上一个图像",
"next": "下一个图像",
"walle": "此图片不能下载用作壁纸。",
"walls": "下载今日美图。仅限用作桌面壁纸。"
}
}
接口返回一个Json对象,其中images为一个图片信息列表。图片信息中,我们关心的是"url",和"copyright"这两个属性。其中url返回的字符串在首部拼接上"https://www.bing.com/"就是图片的url,copyright是图片的描述信息。
5. 创建实体类ImageBean
创建实体类的过程相对简单,直接通过AndroidStudio的GsonFormat插件来自动生成。
public class ImageBean {
private TooltipsBean tooltips; private List images; public TooltipsBean getTooltips() { return tooltips; } public void setTooltips(TooltipsBean tooltips) { this.tooltips = tooltips; } public List getImages() { return images; } public void setImages(List images) { this.images = images; } public static class TooltipsBean { private String loading; private String previous; private String next; private String walle; private String walls; public String getLoading() { return loading; } public void setLoading(String loading) { this.loading = loading; } public String getPrevious() { return previous; } public void setPrevious(String previous) { this.previous = previous; } public String getNext() { return next; } public void setNext(String next) { this.next = next; } public String getWalle() { return walle; } public void setWalle(String walle) { this.walle = walle; } public String getWalls() { return walls; } public void setWalls(String walls) { this.walls = walls; } } public static class ImagesBean { public static final String BASE_URL = "https://www.bing.com/"; private String startdate; private String fullstartdate; private String enddate; private String url; private String urlbase; private String copyright; private String copyrightlink; private String quiz; private boolean wp; private String hsh; private int drk; private int top; private int bot; private List<?> hs; public String getStartdate() { return startdate; } public void setStartdate(String startdate) { this.startdate = startdate; } public String getFullstartdate() { return fullstartdate; } public void setFullstartdate(String fullstartdate) { this.fullstartdate = fullstartdate; } public String getEnddate() { return enddate; } public void setEnddate(String enddate) { this.enddate = enddate; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUrlbase() { return urlbase; } public void setUrlbase(String urlbase) { this.urlbase = urlbase; } public String getCopyright() { return copyright; } public void setCopyright(String copyright) { this.copyright = copyright; } public String getCopyrightlink() { return copyrightlink; } public void setCopyrightlink(String copyrightlink) { this.copyrightlink = copyrightlink; } public String getQuiz() { return quiz; } public void setQuiz(String quiz) { this.quiz = quiz; } public boolean isWp() { return wp; } public void setWp(boolean wp) { this.wp = wp; } public String getHsh() { return hsh; } public void setHsh(String hsh) { this.hsh = hsh; } public int getDrk() { return drk; } public void setDrk(int drk) { this.drk = drk; } public int getTop() { return top; } public void setTop(int top) { this.top = top; } public int getBot() { return bot; } public void setBot(int bot) { this.bot = bot; } public List<?> getHs() { return hs; } public void setHs(List<?> hs) { this.hs = hs; } }}
值得注意的是由于接口并没有返回图片url前缀信息,所以我在ImagesBean的内部手动添加了一个变量BASE_URL来存储图片url前缀信息。
由于项目采用MVVM架构,View层与ViewModel层的通信是通过LiveData这个架构组件实现的,不同于MVP架构中通过接口来通信,所以还要对数据加载的状态和错误信息进行维护。这里创建一个包装类来维护数据的状态和错误信息,以便View层可以对数据加载错误信息进行响应和处理。
public class Data {
private T mData;
private String mErrorMsg;
public Data(T data, String errorMsg) { mData = data; mErrorMsg = errorMsg;}public T getData() { return mData;}public void setData(T data) { mData = data;}public String getErrorMsg() { return mErrorMsg;}public void setErrorMsg(String errorMsg) { mErrorMsg = errorMsg;}
}
Data类的内部非常简单,只有一个泛型T的成员mData来存储数据,和一个String类型的mErrorMsg来存储错误信息。这样View层就可以通过判断mErrorMsg是否为空来判断出数据加载成功与否。
6. 创建数据访问接口ImageRepertory
public class ImageRepertory { private Retrofit mRetrofit; public ImageRepertory() { mRetrofit = new Retrofit.Builder() .baseUrl("https://cn.bing.com/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); } private interface Service { @GET("HPImageArchive.aspx") Observable getImage( @Query("format") String format, @Query("idx") int idx, @Query("n") int n ); } public Observable getImage(String format, int idx, int n) { return mRetrofit.create(Service.class).getImage(format, idx, n); } }
项目采用了Retrofit+Rxjava作为网络访问框架。首先ImageRepertory内部有一个Retrofit实例,并且在构造函数中进行Retrofit的配置和创建。接着创建一个Service接口,其中的getImage方法用来获取图片信息,方法返回一个ImageBean的Observable对象。
7. 编写ImageViewModel
public class ImageViewModel extends ViewModel { private MutableLiveData> mImage; private ImageRepertory mRepertory; private int idx; public ImageViewModel() { mImage = new MutableLiveData<>(); mRepertory = new ImageRepertory(); idx = 0; } public MutableLiveData> getImage() { return mImage; } public void LoadImage() { mRepertory.getImage("js", idx, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ImageBean imageBean) { mImage.setValue(new Data( imageBean.getImages().get(0), null )); } @Override public void onError(Throwable e) { mImage.setValue(new Data( null, e.getMessage() )); } @Override public void onComplete() { } }); } public void nextImage() { mRepertory.getImage("js", ++idx, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ImageBean imageBean) { mImage.setValue(new Data( imageBean.getImages().get(0), null )); } @Override public void onError(Throwable e) { mImage.setValue(new Data( null, e.getMessage() )); idx--; } @Override public void onComplete() { } }); } public void previousImage() { if (idx <= 0) { mImage.setValue(new Data( null, "已经是第一个了" )); return; } mRepertory.getImage("js", --idx, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ImageBean imageBean) { mImage.setValue(new Data( imageBean.getImages().get(0), null )); } @Override public void onError(Throwable e) { mImage.setValue(new Data( null, e.getMessage() )); idx++; } @Override public void onComplete() { } }); }}
首先这个类要继承自android.arch.lifecycle.ViewModel这个类,以便在创建时与View层的生命周期相关联。然后是三个成员变量:mImage这个变量的类型是MutableLiveData用来存放图片信息,以便当信息发生变化时及时通知View层来更新界面;mRepertory这个变量来负责数据访问;idx这个变量来记录当前的图片页码。这三个变量在构造函数中创建并初始化,接着为mImage添加了getter方法以便View层可以对其进行观察与响应。loadImage,nextImage和previousImage这三个方法分别对应图片的加载,下一张和上一张,并且内部通过访问mRepertory的方法来完成数据的访问,又对返回的数据进行判断处理并触发mImage的setValue方法来对数据进行更新。
8. 编写页面
<?xml version="1.0" encoding="utf-8"?>
与正常的XML布局文件不同的是,根标签改成了layout标签,内部有data标签和具体的布局。dat标签内存放Databinding的数据类。除了需要用到的ImagesBean类之外,这里还声明了一个Presenter类用来对界面的用户行为做统一的管理。
***
注意到ImageView的标签内声明了一个url属性,并且和data内的image的数据进行了绑定。然而ImageView并没有这个属性,这时就需要用到Databinding的自定义属性了。
public class BindingAdapter {
@android.databinding.BindingAdapter("url")public static void setImageUrl(ImageView imageView, String url) { Glide.with(imageView.getContext()) .load(url) .into(imageView);}
}
编写一个BindingAdapter类用来声明自定义属性,并使用@android.databinding.BindingAdapter注解来让编译器知道你的属性名。在方法中来对属性值进行处理,这里使用了Glide来进行网络图片的加载。
9. 编写ImageActivity
public class ImageActivity extends AppCompatActivity { private ActivityImageBinding mBinding; private ImageViewModel mViewModel; private ProgressDialog mProgressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_image); mViewModel = new ViewModelProvider( this, new ViewModelProvider.AndroidViewModelFactory(getApplication()) ).get(ImageViewModel.class); mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage("加载中"); mViewModel.getImage().observe(this, new Observer>() { @Override public void onChanged(@Nullable Data imagesBeanData) { if (imagesBeanData.getErrorMsg() != null) { Toast.makeText(ImageActivity.this, imagesBeanData.getErrorMsg(), Toast.LENGTH_SHORT).show(); mProgressDialog.dismiss(); return; } mBinding.setImageBean(imagesBeanData.getData()); setTitle(imagesBeanData.getData().getCopyright()); mProgressDialog.dismiss(); } }); mBinding.setPresenter(new Presenter()); mProgressDialog.show(); mViewModel.loadImage(); } public class Presenter { public void onClick(View view) { mProgressDialog.show(); switch (view.getId()) { case R.id.btn_load: mViewModel.loadImage(); break; case R.id.btn_previous: mViewModel.previousImage(); break; case R.id.btn_next: mViewModel.nextImage(); break; default: break; } } }}
三个成员变量:mBinding数据绑定对象,用来实现数据绑定;mViewModel用来获取数据,实现与数据层的解耦;mProgressDialog用来弹出加载提示框。这三个变量在oncreate方法中初始化,mBinding用DataBindingUtil的setContentView方法实现视图层的绑定;mViewModel要使用ViewModelProvider的get方法完成创建。接着对ViewModel中的LiveData进行观察,在observe方法中处理错误和数据的绑定。内部类Presenter用来对点击事件进行响应,并且也要在oncreate方法里与mBinding进行绑定。
注意:xml文件中一定不能出现与业务相关的代码!比如直接将ViewModel的访问数据的方法在xml中与按钮的点击事件进行绑定,这种方做法是不可取的,因为XML文件的作用应该只是进行数据的显示和用户的交互,而访问数据这种和业务相关的操作不应出现在XML文件中。
转载于:https://www.cnblogs.com/dev-njp/p/8783341.html
更多相关文章
- “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
- Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
- 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
- 如何把批量数据导入到android 的 sqlite 数据库
- Android窗口机制之由setContentView引发的Window,PhoneWindow,Deco
- Android完全关闭应用程序
- Android(安卓)MediaPlayer 字幕同步
- Android(安卓)8.1 来电默认全屏显示 如何修改
- Android(安卓)Studio finish()方法的使用与解决app点击“返回”,