欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Jetpack(2):DataBinding的基本使用

程序员文章站 2022-05-14 15:20:03
...

Android DataBinding 从入门到进阶

DataBinding 介绍

DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常

DataBinding入门

1. 启用 DataBinding

启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持

android {
 dataBinding {
    enabled = true
 }
}

就是这么简单,一个简单的databinding配置之后,就可以开始使用数据绑定了。

2. 布局文件:生成 DataBinding 需要的布局规则

我们来看看布局文件该怎么写,首先布局文件不再是以传统的某一个容器作为根节点,而是使用layout作为根节点,在layout节点中我们可以通过data节点来引入我们要使用的数据源。

打开布局文件,选中根布局的 ViewGroup,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则。

Android Jetpack(2):DataBinding的基本使用

<?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>

    </data>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

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

和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道。

3. 定义一个实体类

要使用数据绑定,我们得首先创建一个实体类:

data class User(val name:String,val age:Int)

4. 在布局文件的data 标签里声明要使用到的变量名、类的全路径,设置数据

在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。

这里声明了一个 User 类型的变量 user,我们要做的就是使这个变量与TextView 控件挂钩,通过设置 user的变量值同时使 TextView 显示相应的文本。

通过 @{user.name} 使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法。

<?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="user"
            type="com.example.jetpack.bean.User" />
    </data>

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

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <!--注意:这里age是int类型,必须转化为String,否则会运行时异常-->
        <TextView
            android:id="@+id/tvAge"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{String.valueOf(user.age)}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvName" />

        <TextView
            android:id="@+id/tvPhoneNum"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.phoneNum==null?user.phoneNum:`17817318877`}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvAge" />

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

5. Activity 中通过 DataBindingUtil 设置布局文件

Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法。

每个数据绑定布局文件都会生成一个绑定类,ViewDataBinding 的实例名是根据布局文件名来生成,将之改为首字母大写的驼峰命名法来命名,并省略布局文件名包含的下划线。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding=DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)

        binding.user= User("赵丽颖",20,"17817318859")
    }
}

一个简单的dataBinding案例就已经完成。

其他用法

在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。

import

如果 User 类型要多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用

  <data>
        
        <import type="com.example.jetpack.bean.User"/>

        <variable
            name="user"
            type="User" />
    </data>

alias

先使用import节点将User导入,然后直接使用即可。但是如果这样的话又会有另外一个问题,假如我有两个类都是User,这两个UserBean分属于不同的包中,又该如何?这时候就要用到alias了。

在import节点中还有一个属性叫做alias,这个属性表示我可以给该类取一个别名,我给User这个实体类取一个别名叫做Lenve,这样我就可以在variable节点中直接写Lenve了。

    <data>
        
        <import type="com.example.jetpack.bean.User" alias="Lenve"/>

        <variable
            name="user"
            type="Lenve" />
    </data>

设置默认值

由于 TextView在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号。

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name,default=morenzhi}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

自定义 ViewDataBinding 的实例名

可以通过如下方式自定义 ViewDataBinding 的实例名:

<data class="CustomBinding">

</data>

字符串拼接

如activity_main中如下属性

  android:text="@{`名字`+user.name}"

三目运算

       <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.phone==null?user.phone:`147522444`}"
            android:textSize="18sp" />

代码中使用控件

使用binding对象可获取布局文件中的各个对象,根据控件设置的id来获取。如

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
binding.btn.setOnClickListener {
       Toast.makeText(this,"点击了按钮",Toast.LENGTH_SHORT).show()
}

BindingAdapter

dataBinding 提供了 BindingAdapter 这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用。

对于一个 ImageView ,我们希望在某个变量值发生变化时,可以动态改变显示的图片,此时就可以通过 BindingAdapter 来实现。

通过 BindingAdapter 来实现加载图片

在java中使用:

在java中使用dataBinding展示图片很简单,只需要配置一个静态的BindingAdapter就可以了。

需要先定义一个静态方法,为之添加 BindingAdapter 注解,注解值是为 ImageView 控件自定义的属性名,而该静态方法的两个参数可以这样来理解:

当 ImageView 控件的 url 属性值发生变化时,dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法,从而可以在此动态改变 ImageView 的相关属性。

public class DataBindingUtils {

    @BindingAdapter("imageUrl")  //imageUrl:控件的属性名
    public static void loadImage(ImageView imageView, String url) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .into(imageView);
    }
}

在kotlin中使用:

首先:kotlin中没有static关键字,但是提供了companion object{}代码块和使用object关键字。

object关键字声明一种特殊的类,这个类只有一个实例,因此看起来整个类就好像是一个对象一样,这里把类声明时的class关键字改成了object,这个类里面的成员默认都是static的。

@JvmStatic注解:与伴生对象搭配使用,将变量和函数声明为真正的JVM静态成员。

要加上kapt插件:

apply plugin: 'kotlin-kapt'

第一种方式:使用 companion object

class DataBindingUtils {

    companion object {
        @BindingAdapter("imageUrl")
        @JvmStatic
        fun loadImage(view: ImageView, url: String) {
            Glide.with(view.context).load(url).into(view)
        }
    }
}

第二种方式:使用object

object DataBindingUtils {

    @BindingAdapter("imageUrl")
    @JvmStatic
    fun loadImage(view: ImageView, url: String) {
        Glide.with(view.context).load(url).into(view)
    }
}
val url="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
binding.user= User("赵丽颖",20,"17817318859",url)

在 xml 文件中关联变量值,当中, app 这个名称可以自定义:

       <ImageView
            android:id="@+id/ivNet"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            app:imageUrl="@{user.url}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn" />

BindingAdapter 更为强大的一点是可以覆盖 Android 原先的控件属性

例如,可以设定每一个 Button 的文本都要加上后缀:“-Button”

    @BindingAdapter("android:text")
    public static void setText(TextView view, String text) {
        view.setText(text + "赵丽颖");
    }
     <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name,default=morenzhi}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

这样,整个工程中使用到了 “android:text” 这个属性的控件,其显示的文本就会多出一个后缀-赵丽颖。

可以用这个属性来加载本地图片:

public class ImageViewAdapter {

    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, Bitmap bitmap) {
        view.setImageBitmap(bitmap);
    }
 
    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, int resId) {
        view.setImageResource(resId);
    }
 
 
    @BindingAdapter("imageUrl")
    public static void setSrc(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url)
                .placeholder(R.mipmap.ic_launcher)
                .into(imageView);
    }
 
 
    @BindingAdapter({"app:imageUrl", "app:placeHolder", "app:error"})
    public static void loadImage(ImageView imageView, String url, Drawable holderDrawable, Drawable errorDrawable) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(holderDrawable)
                .error(errorDrawable)
                .into(imageView);
    }
 
 
}

Databinding 同样是支持在 Fragment

class DemoFrgament : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = DataBindingUtil.inflate<FragmentDemoBinding>(
            inflater,
            R.layout.fragment_demo,
            container,
            false
        )
        return binding.root
    }
}

DataBinding事件绑定

严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已,事件绑定可用于以下多种回调事件:

  • android:onClick
  • android:onLongClick
  • android:afterTextChanged
  • android:onTextChanged

Databinding事件绑定,分两种方式:方法引用和监听绑定,下面分别用案例介绍两种事件绑定的异同。

方式1:直接获取控件设置点击事件

binding.btn1.setOnClickListener {
            Toast.makeText(this,"点击了按钮1",Toast.LENGTH_SHORT).show()
}

方式2:方法引用

传入OnClickListener的变量:

<variable
        name="listener"
        type="android.view.View.OnClickListener" />

在Button中给android:onClick设置listener的变量:

<Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{listener}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{listener}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

通过setListener传入点击监听给listener对象:

    binding.setListener {
            when (it.id) {
                R.id.btn1 -> Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
                R.id.btn2 -> Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()

            }
        }

方式3:方法引用

   <variable
            name="handlers"
            type="com.example.jetpack.MainActivity" />

调用语法可以是@{handlers::onClickFriend}或者@{handlers.onClickFriend}:

  <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickFriend}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickFriend}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        binding.user = User("赵丽颖", 20, "17817318859", url)

        binding.handlers = this

    }
    
    fun onClickFriend(view: View) {
        when (view.id) {
            R.id.btn1 -> Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
            R.id.btn2 -> Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()

        }
    }
}

方式4:方法引用

    <data>
        <variable
            name="user"
            type="com.example.jetpack.bean.User" />

        <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    </data>

   <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="改变name属性"
            android:onClick="@{handlers.onClickChangeName}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickChangAage}"
            android:text="改变age属性"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

在 Activity 内部新建一个类来声明 onClickChangeName() 和 onClickChangAage() 事件相应的回调方法:

class MainActivity : AppCompatActivity() {

    lateinit var user: User

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"

        user = User("赵丽颖", 20, "17817318859", url)
        binding.user = user

        binding.handlers = MyClickHandlers()

    }

    inner class MyClickHandlers {
        fun onClickChangeName(v: View?) {
            user.name = "赵丽颖2"
            binding.user=user
        }

        fun onClickChangAage(v: View?) {
            user.age = 18
            binding.user=user
        }
    }
}

方式5:方法引用

把回调方法单独写到一个接口。

  <variable
            name="handlers"
            type="com.example.jetpack.UserClickListener" />

 <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按钮"
            android:onClick="@{handlers.userClicked}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::userClicked}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

interface UserClickListener {

    fun userClicked(view: View?)
}
class MainActivity : AppCompatActivity(), UserClickListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

       val  binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"

        val user = User("赵丽颖", 20, "17817318859", url)
        binding.user = user

        binding.handlers = this

    }

    override fun userClicked(view: View?) {
        Toast.makeText(this, "方法引用",Toast.LENGTH_SHORT).show();
    }
}

方式6:监听绑定(重要)

将对象直接传回点击方法中。

   <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{()->handlers.showUser(user)}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        var user = User("赵丽颖", 20, "17817318859", url)
        binding.user = user

        binding.handlers = MyClickHandlers()

    }

    inner class MyClickHandlers {
        fun showUser(user: User) {
            Toast.makeText(this@MainActivity, user.name, Toast.LENGTH_SHORT).show()
        }

    }
}