目标
通过一个小案例学习使用Android DataBinding和JectPack组件。
案例将实现加载Bing每日一图的功能。
成果

过程
启用DataBinding
1、APP的gradle文件中配置
1 2 3 4 5 6 7
| android{ ... dataBinding { enabled true } ... }
|
2、Kotlin启用kapt(Java不需要)
1
| apply plugin: 'kotlin-kapt'
|
使用的库
1 2 3 4 5 6 7 8
| //图片加载 implementation 'com.github.bumptech.glide:glide:4.9.0' //ViewModelActivity implementation "android.arch.lifecycle:extensions:1.1.1" //网络请求 implementation 'com.squareup.retrofit2:retrofit:2.5.0' //GSON解析 implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
|
一些简单的封装
ViewModel需要使用到的数据的简单封装
这样简单封装下,可以通用到其他的模块中,当然不封装也是可以使用的,这是学习了,就按照工作使用的通用封装一下,方便其他时候直接拿来用
1
| class Data<T>(var data: T ?= null, var msg: String ?= null)
|
Activity绑定ViewModel的封装
不算是封装,只是简单的扩展,同理Fragment也可以这样用(因为每次加载ViewModel时写的太长,所以写个扩展方法)
1 2
| inline fun <reified T : ViewModel> FragmentActivity.bindViewModel() = ViewModelProviders.of(this).get(T::class.java)
|
网络请求
Bing的每日一图的请求地址格式为
https://cn.bing.com/HPImageArchive.aspx?format=js&idx=1&n=1
简单的分析就是
BaseUrl ==> https://cn.bing.com/
请求的数据定位 HPImageArchive.aspx
请求格式 format(JSON,XML等等)
请求的ID idx
每次请求的个数 n
尝试再浏览器进行简单的请求后,返回的JSON如下
1
| {"images":[{"startdate":"20190325","fullstartdate":"201903251600","enddate":"20190326","url":"/th?id=OHR.SakuraFes_ZH-CN1341601988_1920x1080.jpg&rf=NorthMale_1920x1080.jpg&pid=hp","urlbase":"/th?id=OHR.SakuraFes_ZH-CN1341601988","copyright":"目黑川上盛开的樱花,日本东京 (© taketan/Getty Images)","copyrightlink":"http://www.bing.com/search?q=%E6%A8%B1%E8%8A%B1&form=hpcapt&mkt=zh-cn","title":"","quiz":"/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20190325_SakuraFes%22&FORM=HPQUIZ","wp":true,"hsh":"070b68e02215a4d18ee44b6589b0f5be","drk":1,"top":1,"bot":1,"hs":[]}],"tooltips":{"loading":"正在加载...","previous":"上一个图像","next":"下一个图像","walle":"此图片不能下载用作壁纸。","walls":"下载今日美图。仅限用作桌面壁纸。"}}
|
这里我们所关注的只有两个值。
图片描述(copyright)和图片地址(url)。
所以根据返回的JSON定义两个数据类
1 2 3 4 5 6 7 8 9 10 11 12
| data class ImgBean( @SerializedName("url") var url: String, @SerializedName("copyright") var copyright: String )
data class BaseImg( @SerializedName("images") val mutableList: MutableList<ImgBean> )
|
这样分析后,Retrofit就可以完成请求了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET import retrofit2.http.Query
/** * @author : JXD * @date : 2019/3/26 星期二 */ object RetrofitUtil { const val BASE_URL = "https://cn.bing.com/" private val retrofit: Retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build()
private interface BingService { @GET("HPImageArchive.aspx") fun loadImg( @Query("format") format: String, @Query("idx") idx: Int, @Query("n") n: Int ): Call<BaseImg> }
fun loadImg(format: String = "js", idx: Int = 0, n: Int = 1) = retrofit.create(BingService::class.java).loadImg(format, idx, n) }
|
ViewModel + LiveData
在Google发布的JectPack中,ViewModel + LiveData的组合很神奇,可以在数据变化时自动通知更改显示数据,这里就不写生命周期绑定之类的(我也是萌新,不懂),我的简单理解就是,Activity存在时,一直存在,帮忙保存数据,Activity销毁后,切断了异步回调的数据,也就避免内存泄漏啥的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.ViewModel import retrofit2.Call import retrofit2.Callback import retrofit2.Response
/** * @author : JXD * @date : 2019/3/26 星期二 */ class ImgViewModel : ViewModel() { private var mIdx = -1 val mData = MutableLiveData<Data<ImgBean>>() fun loadImg(idx: Int = -1) { RetrofitUtil.loadImg(idx = idx) .enqueue(object : Callback<BaseImg> { override fun onFailure(call: Call<BaseImg>, t: Throwable) { }
override fun onResponse(call: Call<BaseImg>, response: Response<BaseImg>) { val imgBean = response.body() if (imgBean != null) { val img = imgBean.mutableList[0] mData.value = Data(ImgBean(RetrofitUtil.BASE_URL + img.url, img.copyright)) } } }) }
fun next() { ++mIdx if (mIdx > 7) { --mIdx mData.value = Data(msg = "已经是最后一张了") return } loadImg(mIdx) }
fun pre() { --mIdx if (mIdx < -1) { mIdx = -1 mData.value = Data(msg = "已经是第一张了") return } loadImg(mIdx) } }
|
ViewModel定义了三个方法,
加载图片 loadImg(),
下一张 next(),
上一张 pre()
当返回数据,解析成功后,修改MutableLiveData的值,这样就能监控到数据被改变的状态了。
activity_main.xml
DataBinding需要以标签为根
可以理解为传入我们需要传入变量
name 就是变量名,type就是变量类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" > <data> <variable name="img" type="com.syxrobot.mvvmtest.ImgBean"/> </data> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<ImageView app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_width="wrap_content" android:layout_height="wrap_content" url="@{img.url}" /> <Button android:id="@+id/btn_pre" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上一个" android:onClick="mainClick" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" android:layout_marginStart="50dp" />
<Button android:id="@+id/btn_next" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下一个" android:onClick="mainClick" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_marginEnd="50dp" /> </android.support.constraint.ConstraintLayout> </layout>
|
BindingAdapter
ImageView加载图片,我们习惯于使用Glide,加载并做处理等,那么DataBinding如何使用呢。
以上XML中ImageView的url并不存在于它的属性中,使我们定义的BindingAdapter的方法中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import android.databinding.BindingAdapter import android.widget.ImageView import com.bumptech.glide.Glide
/** * @author : JXD * @date : 2019/3/26 星期二 */ object BindingAdapter { @BindingAdapter("url") @JvmStatic fun setImgUrl(img: ImageView, url: String?) { if (null == url){ return } Glide.with(img.context) .load(url) .into(img) } }
|
MainActivity
1、绑定ViewModel
2、绑定DataBinding
3、监听ViewModel的值变化
4、变化时修改Activity的title的值,并加载新图片
5、如果新值为空,那么加载提示信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import android.arch.lifecycle.Observer import android.content.Context import android.databinding.DataBindingUtil import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.View import android.widget.Toast import com.syxrobot.mvvmtest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var mContext: Context private lateinit var mBind: ActivityMainBinding private lateinit var mViewModel: ImgViewModel
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //1 mViewModel = bindViewModel() //2 mBind = DataBindingUtil.setContentView(this, R.layout.activity_main) mContext = this //3 mViewModel.mData.observe(this, Observer<Data<ImgBean>> { //4 it?.data?.let { ig -> mBind.img = ig title = ig.copyright } //5 it?.msg?.let { msg -> log(msg) } }) mViewModel.loadImg() }
private fun log(msg: String) { Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show() }
fun mainClick(v: View) { when (v.id) { R.id.btn_next -> mViewModel.next() R.id.btn_pre -> mViewModel.pre() } } }
|