1.實(shí)現(xiàn)效果
實(shí)現(xiàn)頁(yè)面加載Bing每日一圖的功能
2.項(xiàng)目結(jié)構(gòu)
3.實(shí)現(xiàn)過(guò)程
1.注入依賴
//ViewModel與LiveData
implementation "android.arch.lifecycle:extensions:1.1.1"
//圖片加載
implementation 'com.github.bumptech.glide:glide:4.9.0'
//網(wǎng)絡(luò)請(qǐng)求
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
//GSON解析
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
同時(shí)需要啟用DataBinding
android {
..........
dataBinding {
enabled = true
}
}
2.接口請(qǐng)求
https://cn.bing.com/HPImageArchive.aspx?format=js&idx=1&n=1
其中 format為請(qǐng)求格式绰播,包含JSON、XML等,
idx為請(qǐng)求ID,n為每次請(qǐng)求的個(gè)數(shù)
(在AndroidManifest.xml文件內(nèi)添加網(wǎng)絡(luò)權(quán)限)
接口返回參數(shù)樣式如下
{"images":[{"startdate":"20190724","fullstartdate":"201907241600","enddate":"20190725","url":"/th?id=OHR.CathedralMountBuffalo_ZH-CN4341947983_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","urlbase":"/th?id=OHR.CathedralMountBuffalo_ZH-CN4341947983","copyright":"清晨暴雨中的Cathedral,澳大利亞布法羅山 (? Ilya Genkin/Alamy)","copyrightlink":"https://www.bing.com/search?q=Cathedral&form=hpcapt&mkt=zh-cn","title":"","quiz":"/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20190724_CathedralMountBuffalo%22&FORM=HPQUIZ","wp":true,"hsh":"5c4b7b7d11456918494bc1ecea07951c","drk":1,"top":1,"bot":1,"hs":[]}],"tooltips":{"loading":"正在加載...","previous":"上一個(gè)圖像","next":"下一個(gè)圖像","walle":"此圖片不能下載用作壁紙。","walls":"下載今日美圖。僅限用作桌面壁紙件余。"}}
利用AS內(nèi)GsonFormat插件自動(dòng)生成ImageBean實(shí)體類
3.創(chuàng)建基礎(chǔ)接受類BaseData
public class BaseData<T> {
private T data;
private String message;
public BaseData() {
}
public BaseData(T data, String message) {
this.data = data;
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
4.搭建基礎(chǔ)網(wǎng)絡(luò)請(qǐng)求框架
采用了Retrofit+Rxjava作為網(wǎng)絡(luò)訪問(wèn)框架(都是些基礎(chǔ)使用)
public class NetUtil {
private Retrofit retrofit;
public NetUtil() {
retrofit = new Retrofit.Builder()
.baseUrl("https://cn.bing.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
public interface ImageService {
@GET("HPImageArchive.aspx")
Observable<ImageBean> getBingImage(@Query("format") String format,
@Query("idx") int idx,
@Query("n") int n);
}
public Observable<ImageBean> getBingImage(String format, int idx, int n) {
return retrofit.create(ImageService.class).getBingImage(format, idx, n);
}
}
5.搭建Repository模塊即具體的網(wǎng)絡(luò)請(qǐng)求模塊
public class ImageRepository {
private static final String TAG = "ImageRepository";
private NetUtil netUtil = new NetUtil();
private MutableLiveData<BaseData<ImageBean>> imageBean1 = new MutableLiveData<>();
private int idx = 1;
/**
* 獲取圖片
*/
public MutableLiveData<BaseData<ImageBean>> getBingImage() {
netUtil.getBingImage("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) {
imageBean1.setValue(new BaseData<>(imageBean, null));
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
return imageBean1;
}
/**
* 獲取下一張圖片
*/
public MutableLiveData<BaseData<ImageBean>> getNextImage() {
if (idx > 6) {
imageBean1.setValue(new BaseData<ImageBean>(null, "已經(jīng)是最后一張啦"));
return imageBean1;
}
++idx;
return getBingImage();
}
/**
* 獲取上一張圖片
*/
public MutableLiveData<BaseData<ImageBean>> getPreviousImage() {
if (idx < 1) {
imageBean1.setValue(new BaseData<ImageBean>(null, "已經(jīng)是第一張啦"));
return imageBean1;
}
--idx;
return getBingImage();
}
}
即使Repository模塊看起來(lái)沒(méi)有必要,它也有著重要的作用遭居。它從應(yīng)用程序的其余部分提取數(shù)據(jù)源√淦鳎現(xiàn)在我們的ViewModel不知道數(shù)據(jù)是由NetUtil獲取的,這意味著我們可以根據(jù)需要將其交換為其他實(shí)現(xiàn)俱萍。所以下一步就是創(chuàng)建對(duì)應(yīng)的ViewModel
6.創(chuàng)建對(duì)應(yīng)的ViewModel
public class DemoActivityViewModel extends AndroidViewModel {
private ImageRepository imageRepository ;
//存放從網(wǎng)絡(luò)上獲取的圖片信息
private MutableLiveData<BaseData<ImageBean>> imageBean ;
public DemoActivityViewModel(@NonNull Application application) {
super(application);
imageRepository=new ImageRepository();
imageBean=new MutableLiveData<>();
}
public MutableLiveData<BaseData<ImageBean>> getImageBean() {
return imageBean;
}
/**
* 獲取圖片
*/
public void getImage() {
imageBean = imageRepository.getBingImage();
}
/**
* 獲取下一張
*/
public void getNextImage() {
imageBean = imageRepository.getNextImage();
}
/**
* 獲取上一張
*/
public void getPreviousImage() {
imageBean = imageRepository.getPreviousImage();
}
}
7.Activity內(nèi)使用
public class DemoActivity extends AppCompatActivity {
private ActivityDemoBinding activityDemoBinding;
private DemoActivityViewModel demoActivityViewModel;
private ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityDemoBinding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
demoActivityViewModel = ViewModelProviders.of(this).get(DemoActivityViewModel.class);
//綁定ViewModel
activityDemoBinding.setViewModel(demoActivityViewModel);
//綁定點(diǎn)擊事件
activityDemoBinding.setEventListener(new OnEventListener());
progressDialog = new ProgressDialog(DemoActivity.this);
progressDialog.setMessage("加載中端壳。。枪蘑。损谦。");
//獲取第一張網(wǎng)絡(luò)圖片
demoActivityViewModel.getImage();
progressDialog.show();
demoActivityViewModel.getImageBean().observe(this, new Observer<BaseData<ImageBean>>() {
@Override
public void onChanged(BaseData<ImageBean> imageBeanBaseData) {
if (imageBeanBaseData.getData() != null) {
//1.直接代碼賦值
Glide.with(DemoActivity.this)
.load("https://www.bing.com" + imageBeanBaseData.getData().getImages().get(0).getUrl())
.into(activityDemoBinding.ivImg);
activityDemoBinding.tvContent.setText(imageBeanBaseData.getData().getImages().get(0).getCopyright());
//2.利用DataBinding直接賦值
activityDemoBinding.setImage(imageBeanBaseData.getData().getImages().get(0));
} else {
Toast.makeText(DemoActivity.this, imageBeanBaseData.getMessage(), Toast.LENGTH_SHORT).show();
}
progressDialog.dismiss();
}
});
}
public class OnEventListener {
public void onClick(View view) {
progressDialog.show();
switch (view.getId()) {
case R.id.tv_previous:
//獲取上一張
demoActivityViewModel.getPreviousImage();
break;
case R.id.tv_next:
//獲取下一張
demoActivityViewModel.getNextImage();
break;
default:
break;
}
}
}
}
與其對(duì)應(yīng)的xml文件為:
<?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="viewModel"
type="com.example.myapplication.demo.DemoActivityViewModel" />
<variable
name="image"
type="com.example.myapplication.demo.ImageBean.ImagesBean" />
<variable
name="eventListener"
type="com.example.myapplication.demo.DemoActivity.OnEventListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".demo.DemoActivity">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="Bing每日一圖"
android:textColor="#000000"
app:layout_constraintBottom_toTopOf="@id/iv_img" />
<ImageView
android:id="@+id/iv_img"
android:layout_width="match_parent"
android:layout_height="200dp"
url="@{"https://www.bing.com"+image.url}" //此處需自定義BindingAdapter
app:layout_constraintTop_toBottomOf="@id/tv" />
<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textColor="#000000"
android:textSize="15sp"
android:text="@{image.copyright}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_img" />
<Button
android:id="@+id/tv_previous"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:gravity="center"
android:text="上一張"
android:onClick="@{eventListener::onClick}"
app:layout_constraintEnd_toStartOf="@id/tv_next"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
<Button
android:id="@+id/tv_next"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="15dp"
android:gravity="center"
android:text="下一張"
android:onClick="@{eventListener::onClick}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_previous"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
在ImageView內(nèi)自定義了一個(gè)url屬性,此時(shí)需使用BindingAdapter去自定義實(shí)現(xiàn)岳颇,實(shí)現(xiàn)起來(lái)很簡(jiǎn)單:
public class DataBindingUtil {
@BindingAdapter(value = {"url"})
public static void loadImage(ImageView view, String url) {
Glide.with(view.getContext()).load(url).into(view);
}
}
至此一個(gè)很簡(jiǎn)單的MVVM架構(gòu)的小demo就實(shí)現(xiàn)了照捡。
推薦一個(gè)方便在github上閱讀源碼的谷歌瀏覽器插件:Sourcegraph。這個(gè)插件還支持搜索文件话侧、代碼等栗精,反正比正常閱讀要方便太多了。