什么是DataBinding

DataBinding是Google官方发布的一个框架,是mvvm在android上的一种实现。
主要应用于数据绑定,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰,可以直接绑定数据到xml中,并实现自动刷新。
databinding能够省去findviewbyId,大量减少activity的代码,数据能够单向或双向绑定到layout文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常

DataBinding的使用

基础配置

首先,在我们build.gradle文件添加对DataBinding的支持

如下图

android include中如何使用databinding_xml


然后来到我们需要支持Databinding的xml布局文件中

将光标定位在第一行最开始

然后按下Alt+Enter键

选择Convert to data binding layout

将我们的xml自动转为Databinding的xml

如下图:

android include中如何使用databinding_User_02


这样,基本的配置就完成了

简单使用

下面我们对一个简单的界面做一个数据的绑定
这个界面只有两个TextView
分别显示姓名和年龄
首先,我们定义一个类

class Person {
    var name = "张三"
    var age = 18
    var address = "北京市朝阳区xxx街道xxx号"
}

然后在支持Databinding的xml里的data标签中,导入这个类

<data>
        <variable
            name="person"
            type="com.example.databindingdemo.data.Person" />
    </data>

type这里需要用到类的全路径,name代表给这个类取得别名
使用的时候,也很简单
按照以下格式对TextView的文本进行赋值就好了

android:text="@{person.name}"

然后来到我们的MainActivity代码

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val contentView: ActivityMainBinding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val person = Person()
        person.address="修改后地址:上海市xxx街道"
        contentView.person = person
    }
}

注意这边的setContentView使用的是Databinding的,而不是原来的

最后显示界面如下:

android include中如何使用databinding_xml_03


如果要针对int类型

可以做这样一个工具类:

class AgeUtils {
    companion object {
        @JvmStatic
        public fun getAge(age: Int): String {
            return "年龄:$age"
        }
    }
}

然后在data标签里导入

<import
            type="com.example.databindingdemo.util.AgeUtils"
            />

使用时:

android:text="@{AgeUtils.getAge(person.age)}"

如果要处理Button按钮之类的Onclick方法
也就是事件的处理
也可以先写一个类

class MyOnclickListener(var context: Context) {
    public fun buttonOnclick(view: View) {
        Toast.makeText(context, "触发按钮点击事件", Toast.LENGTH_SHORT).show()
    }
}

然后加上标签

<variable
            name="myOnclickListener"
            type="com.example.databindingdemo.util.MyOnclickListener" />

使用时:

android:onClick="@{myOnclickListener.buttonOnclick}"

然后在Mainactivity里

contentView.myOnclickListener = MyOnclickListener(this)

这样点击事件就实现了
可以看到
这样做实现了和页面的解耦
Activity里面只需要关注业务即可

二级页面绑定

有时候页面嵌套过多,我们会需要用到二级页面
一般我们会用include标签进行二级页面的嵌套

layout="@layout/sub1"

针对这个sub1的二级xml文件,
如果也需要用到Databinding,
我们也需要用快捷键先转成Databinding的xml格式
如果这个sub1页面也需要用到Person类
也需要在data标签里配置

<data>
        <variable
            name="person"
            type="com.example.databindingdemo.data.Person" />
    </data>

然后在主页xml里的include标签里需要添加一行属性

app:类的别名="@{类的别名}"

这个类的别名就是variable标签的name属性
代表把对应的类传递到sub1的xml中
比如这样:

app:person="@{person}"

这样配置后,sub1里面才能正常使用Person类中的属性
注意,如果是标签,则不需要进行这样的传递配置
直接在sub1的xml里面配置import标签就好了
最终是这样:

<data>

        <variable
            name="idol"
            type="com.example.jetpack_databinding.main1.Idol" />

        <import
            type="com.example.jetpack_databinding.main1.StarUtils"/>
    </data>

加载图片

现在来介绍一下如何去用Databinding加载一张图片
首先我们先写好这么一个类

class ImageViewBindingAdapter {
    companion object {
        //加载网络图片
        @BindingAdapter("image")
        @JvmStatic
        fun setImage(imageView: ImageView, url: String) {
            if (!TextUtils.isEmpty(url)) {
                Log.d("ImageViewBindingAdapter", "url:${url}")
            } else {
                Log.d("ImageViewBindingAdapter", "url为空")
            }
        }
    }
}

注意,这和setImage方法上面带了一个@BindingAdapter的注解
我们在注解里写上自己要写的关键字
来到xml,在data标签里自定义一个variable标签
name随便起,type是字段的类型
这里我们要加载网络图片,就设置type为字符串类型,也就是图片的url

<variable
            name="networkImage"
            type="String" />

然后在Imageview里这样配置一个属性

app:image="@{networkImage}"

这里的image就是上面的注解
networkImage就是自定义variable标签的name
最后来到Activity页面

class MainActivity3 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main3)
        val activityMain3Bind =
            DataBindingUtil.setContentView<ActivityMain3Binding>(this, R.layout.activity_main3)
        activityMain3Bind.networkImage="图片的url"

    }
}

在Activity页面,我们对networkImage进行赋值即可
整个流程,我们注意的就是注解+自定义的variable属性要和Imageview里面的标签对应起来
同样的,如果要加载本地图片
我们也可以自定义一个方法,加上注解,方法参数内传入int类型即可

//加载本地图片
        @BindingAdapter("image")
        @JvmStatic
        fun setLocalImage(imageView: ImageView, resId: Int) {
            imageView.setImageResource(resId)
        }

最后,如果我们要动态的判断
有网络时加载网络图片,否则加载本地图片,我们可以这样定义我们的方法

//加载网络图片,没有则加载本地图片
        @BindingAdapter(value = ["image", "defaultImageResource"], requireAll = false)
        @JvmStatic
        fun setImage(imageView: ImageView, url: String?, resId: Int?) {
            if (!TextUtils.isEmpty(url)) {
                Log.d("ImageViewBindingAdapter", "url:${url}")
                Glide.with(imageView.context).load(url).into(imageView)
            } else {
                Log.d("ImageViewBindingAdapter", "url为空2222")
                resId?.let { imageView.setImageResource(it) }
            }
        }

看到这个方法的注解里多了一个requireAll 属性
true代表Imageview里同时定义了image和defaultImageResource时才会进入这个方法
false代表Imageview里只要定义了image和defaultImageResource其中的一个,就会进入这个方法
这也是方便方法的重载
然后我们定义data标签

<data>
        <variable
            name="networkImage"
            type="String" />

        <variable
            name="localImage"
            type="int" />
    </data>

在Imageview里这样写:

app:image="@{networkImage}"
     app:defaultImageResource="@{localImage}"

Activity里对两个标签属性进行赋值即可

class MainActivity3 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main3)
        val activityMain3Bind =
            DataBindingUtil.setContentView<ActivityMain3Binding>(this, R.layout.activity_main3)
        activityMain3Bind.networkImage="图片的url"
        activityMain3Bind.localImage= R.drawable.img1
    }
}

双向绑定

上面讲的用法基本是单项绑定
Databinding提供数据给View
如果我们改变了某个View的属性,Databinding也会对应的做出修改
这样就算是双向绑定了,下面就以Edittext来做个双向绑定的实现介绍

第一种做法

首先我们定义一个User类

class User(var userName:String="")

然后定义一个双向绑定的类

class UserViewmodel:BaseObservable() {
    var user: User = User("Jack")
    @Bindable
    public fun getUserName(): String {
        return user.userName
    }
    public fun setUserName(userName:String?) {
        if (userName!=null&& userName != user.userName){
            user.userName=userName
            Log.d("MyEditText", "set username:${userName}")
            notifyPropertyChanged(BR.userName)
        }
    }
}

注意这边用到了@Bindable注解,表示进行绑定
在修改的地方,调用notifyPropertyChanged方法去通知Databinding刷新数据
然后在xml里导入这个类

<data>
        <variable
            name="userViewModel"
            type="com.example.jetpack_databinding.main4.UserViewmodel" />
    </data>

使用在Edittext上面

android:text="@={userViewModel.userName}"

注意这边是配置的格式是@={}
最后在我们的Activity

class MainActivity4 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val activityMain4Binding =
            DataBindingUtil.setContentView<ActivityMain4Binding>(this, R.layout.activity_main4)
        val userViewmodel = UserViewmodel()
        activityMain4Binding.userViewModel= userViewmodel
        userViewmodel.setUserName("Rose")
        btn.setOnClickListener {
            userViewmodel.setUserName("Rosesssss")
        }
    }
}

这边通过一个按钮btn的点击事件,强行修改userName
最后发现Edittext也会同步btn的修改,这样就完成了双向的绑定

第二种做法

上面这种做法略微繁琐
我们可以做一个优化
这里用到了一个ObservableField的类
我们先对原来的UserViewModel做一个改造

class UserViewmodel{

    var userobservableField: ObservableField<User> = ObservableField<User>()

    constructor() {
        userobservableField.set(User("roses"))
    }
}

导入到xml后,在EditText里面这样配置

android:text="@={userViewModel.userobservableField.userName}"

Activity里面这样写就好了

class MainActivity5 : AppCompatActivity() {
    lateinit var model: UserViewmodel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val activityMain5Binding =
            DataBindingUtil.setContentView<ActivityMain5Binding>(this, R.layout.activity_main5)
        model = UserViewmodel()
        activityMain5Binding.userViewModel = model
        btn.setOnClickListener {
            Log.d("MainActivity5", "" + model.userobservableField.get()?.userName)
            model.userobservableField.set(User("aaaaaa"))
        }
        //监听属性值的变化
        model.userobservableField.addOnPropertyChangedCallback(object :
            Observable.OnPropertyChangedCallback() {
            override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
                Log.d("MainActivity5", "属性值发生变化${model.userobservableField.get()?.userName}")
            }
        })
    }
}

可以看到,ObservableField这个类帮我们完成了@Bindable注解和notifyPropertyChanged这些事
我们只需要关心业务就好了

RecyclerView的绑定

如果我们要对RecyclerView进行绑定
就需要对Adapter做一个修改
我们现在做一个简单的列表来完成基本的实现
首先我们定义一个基本的item实体类

class RvItem(var chName:String,var enName:String,var image:String,var imageint:Int=0)

然后item的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="RvItem"
            type="com.example.jetpack_databinding.main6.RvItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:context=".main6.MainActivity6">


        <ImageView
            android:id="@+id/imageView"
            app:image="@{RvItem.image}"
            app:defaultImageResource="@{RvItem.imageint}"
            android:layout_width="80dp"
            android:layout_height="80dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/ic_launcher_background" />

        <TextView
            android:id="@+id/textView1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:text="@{RvItem.enName}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/imageView"
            app:layout_constraintTop_toTopOf="@+id/imageView" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:text="@{RvItem.chName}"
            app:layout_constraintBottom_toBottomOf="@+id/imageView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/imageView" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

注意这里用到了之前写的绑定图片的ImageViewBindingAdapter
Adapter的代码也贴上
依旧用到了DataBindingUtil这个类

class Rvadapter(var items: List<RvItem>) : RecyclerView.Adapter<MyHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
        val itemBinding: RvItemBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.rv_item,
            parent,
            false
        )
        return MyHolder(itemBinding)
    }

    override fun onBindViewHolder(holder: MyHolder, position: Int) {
        val rvItem = items[position]
        holder.itemBinding?.rvItem = rvItem
    }

    override fun getItemCount(): Int {
        return items.size
    }

    class MyHolder : RecyclerView.ViewHolder {
        var itemBinding: RvItemBinding? = null

        constructor(itemBinding: RvItemBinding) : super(itemBinding.root) {
            this.itemBinding = itemBinding
        }
    }
}

最后贴上我们的Activity代码

class MainActivity6 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val activityMain6Binding =
            DataBindingUtil.setContentView<ActivityMain6Binding>(this, R.layout.activity_main6)

        activityMain6Binding.recycleview.layoutManager=LinearLayoutManager(this)
        val arrayListOf = arrayListOf<RvItem>()
        arrayListOf.add(RvItem("000", "000", "https://img2.baidu.com/it/u=1921532075,1173186660&fm=26&fmt=auto&gp=0.jpg"))
        arrayListOf.add(RvItem("111", "111", "https://img2.baidu.com/it/u=1921532075,1173186660&fm=26&fmt=auto&gp=0.jpg"))
        arrayListOf.add(RvItem("222", "222", "",R.drawable.img1))

        activityMain6Binding.recycleview.adapter = Rvadapter(arrayListOf)

    }
}

最终效果

android include中如何使用databinding_xml_04

DataBinding+ViewModel+LiveData

下面我们用DataBinding+ViewModel+LiveData来做一个篮球比赛的记分牌

界面如下

android include中如何使用databinding_User_05


这边可以支持增加不同的分数,重置分数,以及撤销上一次操作等

下面看具体的实现

首先写好我们的Viewmodel

class ScoreViewModel(
    var aTeamScore: MutableLiveData<Int> = MutableLiveData(),
    var bTeamScore: MutableLiveData<Int> = MutableLiveData()
) : ViewModel() {
    init {
        aTeamScore.value = 0
        bTeamScore.value = 0
    }

    var ateamlastScore = 0
    var bteamlastScore = 0

    fun getaTeamScore(): MutableLiveData<Int> {
        if (aTeamScore == null) {
            aTeamScore = MutableLiveData()
            aTeamScore!!.value = 0
        }
        return aTeamScore!!
    }

    fun getbTeamScore(): MutableLiveData<Int> {
        if (bTeamScore == null) {
            bTeamScore = MutableLiveData()
            bTeamScore!!.value = 0
        }
        return bTeamScore!!
    }

    fun aTeamAddScore(i: Int) {
        saveLastScore()
        aTeamScore.value = aTeamScore.value?.plus(i)
    }

    fun bTeamAddScore(i: Int) {
        saveLastScore()
        bTeamScore.value = bTeamScore.value?.plus(i)
    }

    //撤销
    fun undo() {
        aTeamScore.value = ateamlastScore
        bTeamScore.value = bteamlastScore
    }

    //记录上一次分数
    fun saveLastScore() {
        if (aTeamScore.value != null) {
            ateamlastScore = aTeamScore.value!!
        }
        if (bTeamScore.value != null) {
            bteamlastScore = bTeamScore.value!!
        }
    }

    //重置
    fun reset() {
        aTeamScore.value = 0
        bTeamScore.value = 0
    }
}

然后是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="ScoreViewModel"
            type="com.example.jetpack_databinding.main7.ScoreViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".main7.MainActivity7">

        <TextView
            android:id="@+id/titleleft"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:gravity="center"
            android:text="A队得分"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/titlright"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:gravity="center"
            android:text="B队得分"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/guideline"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/textViewleft"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:gravity="center"
            android:text="@{String.valueOf(ScoreViewModel.getaTeamScore)}"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/titleleft"
            tools:text="0" />

        <TextView
            android:id="@+id/textViewright"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:gravity="center"
            android:text="@{String.valueOf(ScoreViewModel.getbTeamScore)}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/guideline"
            app:layout_constraintTop_toTopOf="@+id/titlright"
            tools:text="0" />


        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideline"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:orientation="vertical"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintGuide_percent="0.5"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.aTeamAddScore(1)}"
            android:text="+1"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textViewleft" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.aTeamAddScore(2)}"
            android:text="+2"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button1" />

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.aTeamAddScore(3)}"
            android:text="+3"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button2" />

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.bTeamAddScore(1)}"
            android:text="+1"

            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guideline"
            app:layout_constraintTop_toBottomOf="@+id/textViewright" />

        <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.bTeamAddScore(2)}"
            android:text="+2"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guideline"
            app:layout_constraintTop_toBottomOf="@+id/button4" />

        <Button
            android:id="@+id/button6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.bTeamAddScore(3)}"
            android:text="+3"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guideline"
            app:layout_constraintTop_toBottomOf="@+id/button5" />

        <Button
            android:id="@+id/button7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.undo()}"
            android:text="撤销"
            app:layout_constraintEnd_toStartOf="@+id/guideline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button3" />

        <Button
            android:id="@+id/button8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:onClick="@{()->ScoreViewModel.reset()}"
            android:text="重置"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/guideline"
            app:layout_constraintTop_toBottomOf="@+id/button6" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

最后是Activity的代码

class MainActivity7 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val activityMain7Binding =
            DataBindingUtil.setContentView<ActivityMain7Binding>(this, R.layout.activity_main7)
        val scoreViewModel =
            ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
                .get(ScoreViewModel::class.java)
        activityMain7Binding.scoreViewModel = scoreViewModel
        activityMain7Binding.lifecycleOwner=this
    }
}

最后,关于Databinding的使用就介绍到这里了。