定义
即数据绑定,使数据对象和xml布局绑定,支持双向绑定,是Android团队实现MVVM架构的一种方法;
优点
省去大量模板代码:findViewById,onClickListener,setText等;
使view与逻辑解耦,不用向MVC那样混乱,也不用向MVP那样定义大量接口;
view与数据对象双向绑定,开发时只需关注数据对象,无需关系view的各种操作;
xml中可以完成简单逻辑(尽量不要在xml中实现逻辑);
简单使用
开启DataBinding支持,在module的build.gradle中加入下面代码并sync project;
android {
...
dataBinding {
enabled = true
}
}
创建一个数据类ArticleItem.kt
data class ArticleItem(val title:String, val author:String,val content:String,)
创建一个Activity,并自动生成布局,在布局文件中将光标移动到根View上,按alt+enter,选择弹出菜单的「Convert to data binding layout」,代码如下:
//在data标签当中声明要使用到的变量、类的全路径
// 为方便ArticleItem的复用,也可以用import方式引入
// 为防止重复还可以为import增加别名
//binding类的名称默认是已布局文件名改完驼峰命名法生成的如:ActivityArticleListBinding
//可以通过如下方式自定义 ViewDataBinding 的实例名
...
在Activity中为articleInfo赋值
class ArticleListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityArticleListBinding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list)
binding.articleInfo= ArticleItem("Android Jetpack系列","今阳",
"Jetpack 是一个由多个库组成的套件;\n" +
"主要包括架构(Architecture)、基础(Foundation)、行为(Behavior) 、界面(UI)四个方面;")
}
}
//DataBinding也支持在Fragment和RecyclerView中使用
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, getContentViewId(), container, false);
return binding.getRoot();
}
单向数据绑定
默认情况下,普通函数和字符串是不可观察的,这就意味着,当您在数据绑定布局中需要使用它们时,只能在新建的时候获取它们的值,但在后续的操作中,却不能得到相应的数据。
Observable有三种实现:BaseObservable、ObservableField、ObservableCollection
BaseObservable
BaseObservable 提供了 notifyChange()(刷新所有的值域)和 notifyPropertyChanged()(只更新对应 BR,该BR通过注释 @Bindable 生成)两个方法;
//1. 自定义Observable
class ArticleItem2(var title: String, author: String, content: String) :
BaseObservable() {
@get:Bindable
var author: String = author
set(value) {
field = value
notifyPropertyChanged(BR.author)
}
@get:Bindable
var content: String = content
set(value) {
field = value
notifyChange()
}
}
2. Activity中创建点击事件调用ArticleItem2的set方法
class ArticleListActivity : AppCompatActivity() {
lateinit var articleInfo: ArticleItem2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityArticleListBinding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list)
articleInfo = ArticleItem2(
"Android Jetpack系列", "今阳",
"Jetpack 是一个由多个库组成的套件;"
)
//可以设置监听器观察属性的更改
articleInfo.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
when {
BR.author == propertyId -> {
LjyLogUtil.d("BR.author")
}
BR.content == propertyId -> {
LjyLogUtil.d("BR.content")
}
BR._all == propertyId -> {
LjyLogUtil.d("BR._all")
}
else -> {
LjyLogUtil.d("propertyId:$propertyId")
}
}
}
})
binding.articleInfo=articleInfo
binding.onClickPresenter=OnClickPresenter()
}
inner class OnClickPresenter {
fun changeTitle() {
articleInfo.title="${articleInfo.title}1"
}
fun changeAuthor() {
articleInfo.author="${articleInfo.author}1"
}
fun changeContent() {
articleInfo.content="${articleInfo.content}1"
}
}
}
//3.xml中增加button并调用点击事件
ObservableField
继承BaseObservable限制较高,需要notify操作,为了使用方便可以使用ObservableField;
是官方对 BaseObservable 中字段的注解和刷新等操作的封装;
官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ;
也可通过 ObservableField 泛型来申明其他类型
//1. 定义数据类
class ArticleItem3(title: String, author: String, content: String) {
val title: ObservableField = ObservableField(title)
val author: ObservableField = ObservableField(author)
val content: ObservableField = ObservableField(content)
}
//2. 修改OnClickPresenter代码
inner class OnClickPresenter {
fun changeTitle() {
articleInfo.title.set("${articleInfo.title.get()}1")
}
fun changeAuthor() {
articleInfo.author.set("${articleInfo.author.get()}1")
}
fun changeContent() {
articleInfo.content.set("${articleInfo.content.get()}1")
}
}
ObservableCollection
dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap
//1. 修改variable标签
//2. 修改Activity中的代码
class ArticleListActivity : AppCompatActivity() {
lateinit var articleInfo: ObservableArrayMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityArticleListBinding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list)
articleInfo = ObservableArrayMap()
articleInfo.apply {
put("title", "Android Jetpack系列")
put("author", "今阳")
put("content", "Jetpack 是一个由多个库组成的套件;")
}
binding.articleInfo = articleInfo
binding.onClickPresenter = OnClickPresenter()
}
inner class OnClickPresenter {
fun changeTitle() {
articleInfo["title"]+="1"
}
fun changeAuthor() {
articleInfo["author"]+="1"
}
fun changeContent() {
articleInfo["content"]+="1"
}
}
}
双向数据绑定
当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据
绑定变量的方式比单向绑定多了一个等号,代码如下:
LiveData 替换 Observable Fields
上面讲了Observable Fields,但是google官方更推荐使用LiveData 替换 Observable Field;
参考google官方文章两步使用 LiveData 替换 Observable Field
LiveData 可以感知生命周期,这一点与 Observable Fields 相比并没有多大优势,因为 Data Binding 原本就可以检查视图活跃情况。 因此对于 LiveData 来说,它的优势在于不仅支持Transformations,而且可以与许多架构组件 (如Room、WorkManager) 相互配合使用。 综上,我们推荐您使用 LiveData。方法也非常简单,只需要两个步骤。
//1. 用 LiveData 替换 Observable Fields
class ArticleItem4(title: String, author: String, content: String) : ViewModel() {
var title: MutableLiveData = MutableLiveData().apply { value = title }
var author: MutableLiveData = MutableLiveData().apply { value = author }
var content: MutableLiveData = MutableLiveData().apply { value = content }
}
//2. 设置 LiveData 的生命周期所有者(lifecycleOwner)
class ArticleListActivity : AppCompatActivity() {
lateinit var articleInfo: ArticleItem4
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityArticleListBinding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list)
//视图的绑定类中包含一个 setLifecycleOwner 方法,想要从数据绑定布局观察 LiveData ,必须使用该方法。
binding.lifecycleOwner = this
articleInfo = ArticleItem4(
"Android Jetpack系列", "今阳",
"Jetpack 是一个由多个库组成的套件;"
)
binding.articleInfo = articleInfo
binding.onClickPresenter = OnClickPresenter()
}
inner class OnClickPresenter {
fun changeTitle() {
articleInfo.title.value+="6"
}
fun changeAuthor() {
articleInfo.author.value+="6"
}
fun changeContent() {
articleInfo.content.value+="6"
}
}
}
事件绑定
事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已,而且我们上面的举例中button的点击事件已有用到
//1. 定义事件方法
inner class OnClickPresenter {
fun changeTitle(articleInfo:ArticleItem4) {
articleInfo.title.value+="6"
}
fun changeAuthor() {
articleInfo.author.value+="6"
}
}
//2. data中引用
//3. view中绑定
BindingAdapter 和 BindingConversion
BindingAdapter
dataBinding 提供了 BindingAdapter 这个注解用于支持自定义属性,或者是修改原有属性;
注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用;
例1:为每个 Button 的文本都要加上后缀:“-Button”
//1. 定义一个方法,类似于扩展函数
class ArticleListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityArticleListBinding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list)
binding.lifecycleOwner = this
var articleInfo: ArticleItem4 = ArticleItem4(
"Android Jetpack系列", "今阳",
"Jetpack 是一个由多个库组成的套件;"
)
binding.articleInfo = articleInfo
}
}
@BindingAdapter("android:text")
fun setText(view: Button, text: String) {
view.text = "$text-Button"
}
//2. xml中设置android:text='@{"title+1"}'
例2:自定义属性
这里借助一个Google 官推的图片库 Coil,这个库完全是用 Kotlin 写的,而且运用了大量 Kotlin 的特性,尤其是协程;
Coil 给 ImageView 加了很多拓展函数,所以我们一行代码便能进行图片加载;
详细使用可以参考:还在用 Glide?看看 Google 官推的图片库 Coil 有何不同!
//1. 添加coil依赖
implementation("io.coil-kt:coil:1.1.1")
//2. 创建辅助的数据类
class ImageBean(url: String) {
var url: MutableLiveData = MutableLiveData().apply { value = url }
}
//3.定义方法并添加注解
@BindingAdapter("url")
fun loadImage(view: ImageView, url: String) {
view.load(url)
LjyLogUtil.d("url:${url}")
}
//4. xml中引用
//5. ImageView中使用
//6. activity代码
lass ArticleListActivity : AppCompatActivity() {
lateinit var articleInfo: ArticleItem4
lateinit var image: ImageBean
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityArticleListBinding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list)
binding.lifecycleOwner = this
articleInfo = ArticleItem4(
"Android Jetpack系列", "今阳",
"Jetpack 是一个由多个库组成的套件;"
)
binding.articleInfo = articleInfo
image = ImageBean("https://pic1.zhimg.com/v2-dc32dcddfd7e78e56cc4b6f689a24979_is.jpg")
binding.image=image
binding.onClickPresenter = OnClickPresenter()
}
inner class OnClickPresenter {
fun changeTitle(articleInfo: ArticleItem4) {
articleInfo.title.value += "6"
image.url.value="https://pic3.zhimg.com/v2-e5656460688d19f7358ab3a6055fe34a_720w.jpg?source=95cc6b4a"
}
fun changeAuthor() {
articleInfo.author.value += "6"
image.url.value="https://pic2.zhimg.com/v2-f6981776beae87401991b426fbe34fdd_720w.jpg?source=95cc6b4a"
}
fun changeContent() {
articleInfo.content.value += "6"
image.url.value="https://pic2.zhimg.com/v2-f2eddc2fe0e509de5bbeeb351ddc2c61_1440w.jpg?source=172ae18b"
}
}
}
BindingConversion
dataBinding 还支持对数据进行转换,或者进行类型转换
@BindingConversion
fun convertStringToDrawable(str: String): Drawable {
return when (str) {
"红色" -> {
ColorDrawable(Color.parseColor("#FF4081"))
}
"蓝色" -> {
ColorDrawable(Color.parseColor("#3F51B5"))
}
else -> {
ColorDrawable(Color.parseColor("#344567"))
}
}
}
@BindingConversion
fun convertStringToColor(str: String): Int {
return when (str) {
"红色" -> {
Color.parseColor("#FF4081")
}
"蓝色" -> {
Color.parseColor("#3F51B5")
}
else -> {
Color.parseColor("#344567")
}
}
}
绑定列表数据
RecyclerView使用BaseRecyclerViewAdapterHelper+DataBinding
自定义Adapter:
class ArticleAdapter(data: MutableList?) :
BaseQuickAdapter(R.layout.layout_item_article, data) {
override fun convert(holder: ArticleItemViewHolder, item: ArticleItem4) {
holder.binding?.articleInfo = item
holder.binding?.executePendingBindings()
}
class ArticleItemViewHolder(view: View) : BaseViewHolder(view) {
val binding: LayoutItemArticleBinding? = DataBindingUtil.bind(view)
}
}
//最新的BaseQuickAdapter提供了上述自定义ViewHolder的实现,BaseDataBindingHolder,可以如下使用:
class ArticleAdapter(data: MutableList?) :
BaseQuickAdapter >(R.layout.layout_item_article, data) {
override fun convert(holder: BaseDataBindingHolder, item: ArticleItem4) {
holder.dataBinding?.articleInfo = item
holder.dataBinding?.executePendingBindings()
}
}
layout_item_article.xml布局
Activity代码如下
class ArticleList2Activity : AppCompatActivity() {
lateinit var mAdapter:ArticleAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LjyLogUtil.d("onCreate")
val binding: ActivityArticleList2Binding =
DataBindingUtil.setContentView(this, R.layout.activity_article_list2)
binding.lifecycleOwner = this
binding.rvArticleList.layoutManager = LinearLayoutManager(this)
val articleList:MutableList = ArrayList()
articleList.add(ArticleItem4("title1","jinYang","content111"))
articleList.add(ArticleItem4("title2","jinYang","content222"))
articleList.add(ArticleItem4("title3","jinYang","content333"))
mAdapter= ArticleAdapter(articleList)
binding.rvArticleList.adapter=mAdapter
binding.onClickPresenter2 = OnClickPresenter2()
}
inner class OnClickPresenter2 {
fun addArticle() {
mAdapter.addData(ArticleItem4("title${mAdapter.data.size}","jinYang","content${mAdapter.data.size}"))
LjyLogUtil.d("addArticle")
}
fun removeArticle() {
mAdapter.removeAt(0)
LjyLogUtil.d("removeArticle")
}
}
}
activity_article_list2.xml布局如下
我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章