獲取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