获取Bing每日一图并显示
java
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'
dataBinding{ enabled=true }
<uses-permission android:name="android.permission.INTERNET"/>
接口地址:https://cn.bing.com/HPImageArchive.aspx?format=js&idx=1&n=1
react
{
"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": "下载今日美图。仅限用做桌面壁纸。"
}
}
建立实体类的过程相对简单,直接经过AndroidStudio的GsonFormat插件来自动生成。
android
public class ImageBean { private TooltipsBean tooltips; private List<ImagesBean> images; public TooltipsBean getTooltips() { return tooltips; } public void setTooltips(TooltipsBean tooltips) { this.tooltips = tooltips; } public List<ImagesBean> getImages() { return images; } public void setImages(List<ImagesBean> 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前缀信息。
git
public class Data{ 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; }
private T mData;
private String mErrorMsg;}
github
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") ObservablegetImage( @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对象。网络
public class ImageViewModel extends ViewModel { private MutableLiveData<Data<ImageBean.ImagesBean>> mImage; private ImageRepertory mRepertory; private int idx; public ImageViewModel() { mImage = new MutableLiveData<>(); mRepertory = new ImageRepertory(); idx = 0; } public MutableLiveData<Data<ImageBean.ImagesBean>> getImage() { return mImage; } public void LoadImage() { mRepertory.getImage("js", idx, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<ImageBean>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ImageBean imageBean) { mImage.setValue(new Data<ImageBean.ImagesBean>( imageBean.getImages().get(0), null )); } @Override public void onError(Throwable e) { mImage.setValue(new Data<ImageBean.ImagesBean>( null, e.getMessage() )); } @Override public void onComplete() { } }); } public void nextImage() { mRepertory.getImage("js", ++idx, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<ImageBean>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ImageBean imageBean) { mImage.setValue(new Data<ImageBean.ImagesBean>( imageBean.getImages().get(0), null )); } @Override public void onError(Throwable e) { mImage.setValue(new Data<ImageBean.ImagesBean>( null, e.getMessage() )); idx--; } @Override public void onComplete() { } }); } public void previousImage() { if (idx <= 0) { mImage.setValue(new Data<ImageBean.ImagesBean>( null, "已是第一个了" )); return; } mRepertory.getImage("js", --idx, 1) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<ImageBean>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ImageBean imageBean) { mImage.setValue(new Data<ImageBean.ImagesBean>( imageBean.getImages().get(0), null )); } @Override public void onError(Throwable e) { mImage.setValue(new Data<ImageBean.ImagesBean>( 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方法来对数据进行更新。架构
<?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" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="imageBean" type="com.njp.mvvm.ImageBean.ImagesBean" /> <variable name="presenter" type="com.njp.mvvm.ImageActivity.Presenter" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView url="@{imageBean.BASE_URL+imageBean.url}" android:layout_width="match_parent" android:layout_height="300dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <Button android:id="@+id/btn_previous" android:layout_width="0dp" android:layout_height="wrap_content" android:onClick="@{presenter.onClick}" android:layout_weight="1" android:text="上一张" /> <Button android:id="@+id/btn_load" android:layout_width="0dp" android:onClick="@{presenter.onClick}" android:layout_height="wrap_content" android:layout_weight="1" android:text="加载" /> <Button android:id="@+id/btn_next" android:layout_width="0dp" android:layout_height="wrap_content" android:onClick="@{presenter.onClick}" android:layout_weight="1" android:text="下一张" /> </LinearLayout> </LinearLayout> </layout>
与正常的XML布局文件不一样的是,根标签改为了layout标签,内部有data标签和具体的布局。dat标签内存放Databinding的数据类。除了须要用到的ImagesBean类以外,这里还声明了一个Presenter类用来对界面的用户行为作统一的管理。
***
注意到ImageView的标签内声明了一个url属性,而且和data内的image的数据进行了绑定。然而ImageView并无这个属性,这时就须要用到Databinding的自定义属性了。
app
public class BindingAdapter {}
@android.databinding.BindingAdapter("url") public static void setImageUrl(ImageView imageView, String url) { Glide.with(imageView.getContext()) .load(url) .into(imageView); }
框架
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<Data<ImageBean.ImagesBean>>() { @Override public void onChanged(@Nullable Data<ImageBean.ImagesBean> 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进行绑定。
mvvm