1.实现效果
实现页面加载Bing每日一图的功能
2.项目结构
3.实现过程
1.注入依赖
//ViewModel与LiveData
implementation "android.arch.lifecycle:extensions:1.1.1"
//图片加载
implementation 'com.github.bumptech.glide:glide:4.9.0'
//网络请求
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'
同时需要启用DataBinding
android {
..........
dataBinding {
enabled = true
}
}
2.接口请求
https://cn.bing.com/HPImageArchive.aspx?format=js&idx=1&n=1
其中 format为请求格式,包含JSON、XML等,
idx为请求ID,n为每次请求的个数
(在AndroidManifest.xml文件内添加网络权限)
接口返回参数样式如下
{"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":"上一个图像","next":"下一个图像","walle":"此图片不能下载用作壁纸。","walls":"下载今日美图。仅限用作桌面壁纸。"}}
利用AS内GsonFormat插件自动生成ImageBean实体类
3.创建基础接受类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.搭建基础网络请求框架
采用了Retrofit+Rxjava作为网络访问框架(都是些基础使用)
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模块即具体的网络请求模块
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, "已经是最后一张啦"));
return imageBean1;
}
++idx;
return getBingImage();
}
/**
* 获取上一张图片
*/
public MutableLiveData<BaseData<ImageBean>> getPreviousImage() {
if (idx < 1) {
imageBean1.setValue(new BaseData<ImageBean>(null, "已经是第一张啦"));
return imageBean1;
}
--idx;
return getBingImage();
}
}
即使Repository模块看起来没有必要,它也有着重要的作用。它从应用程序的其余部分提取数据源。现在我们的ViewModel不知道数据是由NetUtil获取的,这意味着我们可以根据需要将其交换为其他实现。所以下一步就是创建对应的ViewModel
6.创建对应的ViewModel
public class DemoActivityViewModel extends AndroidViewModel {
private ImageRepository imageRepository ;
//存放从网络上获取的图片信息
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内使用
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);
//绑定点击事件
activityDemoBinding.setEventListener(new OnEventListener());
progressDialog = new ProgressDialog(DemoActivity.this);
progressDialog.setMessage("加载中。。。。");
//获取第一张网络图片
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;
}
}
}
}
与其对应的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内自定义了一个url属性,此时需使用BindingAdapter去自定义实现,实现起来很简单:
public class DataBindingUtil {
@BindingAdapter(value = {"url"})
public static void loadImage(ImageView view, String url) {
Glide.with(view.getContext()).load(url).into(view);
}
}
至此一个很简单的MVVM架构的小demo就实现了。
推荐一个方便在github上阅读源码的谷歌浏览器插件:Sourcegraph。这个插件还支持搜索文件、代码等,反正比正常阅读要方便太多了。