使用笔记:DataBinding在Kotlin中的使用

DataBinding是一个实现数据和UI绑定的框架,同时也是实现MVVM模式所依赖的工具。
今天文章主要记录一下DataBinding在Kotlin中的简单使用。

Demo下载


配置

在应用的build.gradle文件中添加以下代码:

android {
    ...
    dataBinding {
        enabled = true
    }
    ...
}

基本使用功能

  1. 替代findViewById
    布局文件根节点必须是<layout>,同时layout只能包含一个View标签,不能直接包含<merge>。layout里面的View标签就是我们要展示的布局。
<?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"
    >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <TextView
            android:id="@+id/tv_nick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
    </LinearLayout>
</layout>

布局通过DataBindingUtils.setContentView()加载到代码中,而且会生成对应一个Binding对象,对象名是布局文件文称加上Binding后缀。
通过Binding对象.id名称,就相当于拿到了布局中指定id的控件了,使用起来和findViewById获取的控件是一样的。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        dataBinding.tvNick.text = "这是昵称"
    }
}

  1. 绑定基本数据类型及String类型
    在布局文件根节点<layout>下添加<data>标签,里面就是要跟UI进行绑定的数据。name是自定义的数据名字,type表示该数据的类型。
    在布局中是通过@{}来绑定数据的,{}中是布局中该控件属性对应的数据类型数据
<?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="content"
            type="String" />
        <variable
            name="enabled"
            type="boolean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
        <Button
            android:id="@+id/bt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:clickable="@{enabled}"
            android:text="@{content}" />
    </LinearLayout>
</layout>

同样通过Binding对象.name名字,就可以拿到指定名字的数据对象了,通过给数据对象赋值,就能改变对应绑定的UI属性。
给控件设置点击事件,发现其实点击无效,因为在布局文件中给cilckable属性绑定了enabled数据,而在代码中设置enabled数据值为false,所以点击事件无效。enabled数据值改为true后,点击事件就会有效。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        dataBinding.enabled = false
        dataBinding.content = "这是一个按钮"
        dataBinding.bt.setOnClickListener {
            Toast.makeText(this, "成功点击了", Toast.LENGTH_SHORT).show()
        }
    }
}

  1. 绑定Model数据
    Model数据类型:
class User constructor(var name: String, var nick: String, var isMale: Boolean, var age: Int)

<data>标签中数据type为model数据类型的全路径,或者通过<import>标签先导入model数据类型,然后type指定为model数据类型即可。
在布局中是通过@{}来绑定数据,如果绑定的属性需要String类型,但数据类型不是String类型,必须转化为String。

<?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>
        <import type="com.example.myapplication.User" />

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <!-- boolean 要转为String来显示 不然编译异常-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.isMale)}" />

        <!-- age是int类型 必须转化为String 不然编译异常-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.age)}" />
    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        val user = User("名称", "昵称", true, 20)
        dataBinding.user = user
    }
}

  1. 绑定接口数据
    接口数据:
interface EventListener {
    fun onClick1(view: View)
    fun onClick2(view: View)
    fun onClick3(string: String)
}

同样type需要为接口的全路径,然后在布局中通过@{}来绑定数据。接口绑定有三种写法:event.onClick1、event::onClick2、()->event.onClick3(title4)

<?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="event"
            type="com.example.myapplication.EventListener" />

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

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

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

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <Button
            android:id="@+id/bt1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{event.onClick1}"
            android:text="@{title1}" />

        <Button
            android:id="@+id/bt2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{event::onClick2}"
            android:text="@{title2}" />

        <Button
            android:id="@+id/bt3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{()->event.onClick3(title4)}"
            android:text="@{title3}" />
    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        dataBinding.title1 = "这是按钮1"
        dataBinding.title2 = "这是按钮2"
        dataBinding.title3 = "这是按钮3"
        dataBinding.title4 = "这是点击更新了"
        dataBinding.event = object : EventListener {
            override fun onClick1(view: View) {
                Toast.makeText(this@MainActivity, "绑定接口1点击成功", Toast.LENGTH_SHORT).show()
            }

            override fun onClick2(view: View) {
                Toast.makeText(this@MainActivity, "绑定接口2点击成功", Toast.LENGTH_SHORT).show()
            }

            override fun onClick3(string: String) {
                dataBinding.title3 = string
            }
        }
    }
}

  1. 绑定静态方法
    静态方法:
class Utils {
    companion object {
        //注解方式实现静态方法只能用在单例类中或companion object关键中
        @JvmStatic
        fun getContent(user: User): String {
            return user.name
        }
    }
}

在<data>标签中通过<import>导入路径,在布局中通过@{}直接调用静态方法即可。

<?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>
        <import type="com.example.myapplication.Utils" />

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{Utils.getContent(user)}" />
    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        val user = User("名称", "昵称", true, 20)
        dataBinding.user = user
    }
}

  1. 通过运算符操作数据
    在布局中通过@{}绑定数据时,用``字符包裹的表示字符串,可用作拼接字符串。
<?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.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`拼接了`+user.name}" />
    </LinearLayout>
</layout>

此外支持的运算符表达式还有:

  • 数学计算 + - / * %
  • 字符串连接 +
  • 逻辑表达式 && ||
  • 位操作符 & | ^
  • 一元运算符 + - ! ~
    -位移操作符 >> >>> <<
  • 比较操作符 == > < >= <=
  • instanceof
  • 分组操作符 ()
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三目运算符 ?:
  • 聚合判断语法 ??

  1. 绑定List/Map等集合数据
    需要在<data>中写全路径,或者<import>引入List和Map的全路径,然后泛型<>的表达需要通过转义字符,有两种方式,如下所见。
    List集合既可以和数组一样通过索引获取值list[index]方式,也可以调用API。
    Map集合既可以通过map[key]的方式,也可以通过调用API。
<?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>
        <import type="java.util.ArrayList" />
        <!--泛型的支持需要通过转义字符才行,如:&lt;数据类型> 或者 &lt;数据类型&gt; -->
        <variable
            name="list"
            type="ArrayList&lt;String>" />

        <import type="java.util.Map" />

        <variable
            name="map"
            type="Map&lt;String,String&gt;" />

        <variable
            name="arrays"
            type="String[]" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <!--List集合既可以和数组一样通过索引获取值list[index]方式,也可以调用API-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list[0]}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list.get(1)}" />

        <!--Map集合既可以通过map[key]的方式,也可以通过调用API-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{map[`name`]}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{map.get(`name`)}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{arrays[0]}" />
    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        val list = ArrayList<String>()
        list.add("list-first")
        list.add("list-second")
        val map = HashMap<String, String>()
        map["name"] = "map-name"
        val arrays = arrayOf("arrays-first")
        dataBinding.list = list
        dataBinding.map = map
        dataBinding.arrays = arrays
    }
}

  1. Observable数据改变,动态更新UI
    常规的数据Model与UI绑定之后,数据再次改变,绑定的UI并不会自动更新。但是实现了Observable的数据改变数据内容之后,绑定的UI也会动态更新。
    Observable是一个接口,其子类有BaseObservable, ObservableField, ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable, ObservableArrayList, ObservableArrayMap。
    Observable数据模型:
/**
 *
使用ObservableField<>,泛型可以填入自己需要的类型,注意必须要初始化。
对于基本数据类型也可以直接使用ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble和ObservableParcelable。
 */
class ObservableModel {
    var field = ObservableField<String>()
    var age = ObservableInt()
}

<data>中引入需要的数据模型,注意ObservableArrayList,ObservableArrayMap泛型的表达需要转义字符。

<?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="observableModel"
            type="com.example.myapplication.ObservableModel" />

        <variable
            name="observableList"
            type="androidx.databinding.ObservableArrayList&lt;String&gt;" />

        <variable
            name="observableMap"
            type="androidx.databinding.ObservableArrayMap&lt;String,String&gt;" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <Button
            android:id="@+id/bt_observable"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点击进行数据更新" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observableModel.field}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(observableModel.age)}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observableList[0]}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observableMap[`name`]}" />
    </LinearLayout>
</layout>

点击操作之后更改数据,对应绑定的UI也会动态更新。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        val observableModel = ObservableModel()
        observableModel.field.set("更新前")
        observableModel.age.set(5)
        val observableList = ObservableArrayList<String>()
        observableList.add("更新前")
        val observableMap = ObservableArrayMap<String, String>()
        observableMap["name"] = "更新前"
        dataBinding.observableModel = observableModel
        dataBinding.observableList = observableList
        dataBinding.observableMap = observableMap
        dataBinding.btObservable.setOnClickListener {
            observableModel.field.set("更新后")
            observableModel.age.set(10)
            observableList[0] = "更新后"
            observableMap["name"] = "更新后"
        }
    }
}

  1. 在Fragment中使用DataBinding
    在Activity中,布局通过DataBindingUtils.setContentView()加载到代码中,而且会生成对应一个Binding对象,对象名是布局文件文称加上Binding后缀。
    在Fragment中,布局通过DataBindingUtil.inflate()加载到代码中,通过dataBinding.root获取到布局View。
override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        //fragment中通过DataBinding加载布局
        val dataBinding: DataBindingFragmentBinding =
            DataBindingUtil.inflate(inflater, R.layout.data_binding_fragment, container, false)
        return dataBinding.root
    }

  1. 在RecyclerView中使用DataBinding
    RecyclerView的Item的布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="model"
            type="com.example.databindingdemo.ItemModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:focusable="true"
        android:onClick="@{model::onItemClick}"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{model.content}" />

        <View
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:layout_marginTop="5dp"
            android:background="#C8C8C8" />
    </LinearLayout>
</layout>

Adapter的定义方式和普通方式相同,都是继承了RecyclerView.Adapter<ViewHolder>。
自定义ViewHolder传入Binding对象,通过Binding对象拿到UI和绑定的数据。
onCreateViewHolder方法中通过DataBindingUtil.inflate()加载布局,生成自定义ViewHolder,传入Binding对象。
onBindViewHolder中通过ViewHolder拿到Binding对象,传入绑定的数据,更新UI。

private inner class ItemAdapter(var context: Context, var list: List<ItemModel>) :
        RecyclerView.Adapter<ItemViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
            val dataBinding: ItemRvBinding =
                DataBindingUtil.inflate(
                    LayoutInflater.from(context),
                    R.layout.item_rv,
                    parent,
                    false
                )
            return ItemViewHolder(dataBinding)
        }

        override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
            val binding = holder.binding
            binding.model = list[position]
        }

        override fun getItemCount(): Int = list.size

    }

    private inner class ItemViewHolder(var binding: ItemRvBinding) :
        RecyclerView.ViewHolder(binding.root)
override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        //fragment中通过DataBinding加载布局
        val dataBinding: DataBindingFragmentBinding =
            DataBindingUtil.inflate(inflater, R.layout.data_binding_fragment, container, false)
        //数据
        val list = ArrayList<ItemModel>()
        list.add(ItemModel("first item"))
        list.add(ItemModel("second item"))
        list.add(ItemModel("third item"))
        dataBinding.rv.apply {
            layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
            adapter = ItemAdapter(context, list)
            addItemDecoration(object : RecyclerView.ItemDecoration() {
                override fun getItemOffsets(
                    outRect: Rect,
                    view: View,
                    parent: RecyclerView,
                    state: RecyclerView.State
                ) {
                    super.getItemOffsets(outRect, view, parent, state)
                    val position: Int = parent.getChildLayoutPosition(view)
                    if (position != 0) {
                        outRect.top = 30
                    }
                }
            })
        }
        return dataBinding.root
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容