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

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. 如何把批量数据导入到android 的 sqlite 数据库
  5. Android窗口机制之由setContentView引发的Window,PhoneWindow,Deco
  6. Android完全关闭应用程序
  7. Android(安卓)MediaPlayer 字幕同步
  8. Android(安卓)8.1 来电默认全屏显示 如何修改
  9. Android(安卓)Studio finish()方法的使用与解决app点击“返回”,

随机推荐

  1. android百度地图api实现查询经过某站点的
  2. NoScript For Android发布
  3. Android自动dump hprof文件的功能实现
  4. 利用旧版Android漏洞的E-Z-2-Use攻击代码
  5. Android五分钟轻松教会你掌握WebView与js
  6. android application类和全局数据使用
  7. Android日记之2012/02/11——浅谈Iterato
  8. 直播APP开发公司关于Android各版本关于沉
  9. Android(安卓)parse XML
  10. android中.9png