美文网首页
Data binding 基础使用

Data binding 基础使用

作者: 13kmsteady | 来源:发表于2018-11-26 21:19 被阅读28次

前言

Data binding 库是官方推出的数据绑定框架,允许在 xml 中指定 UI 组件的数据来源。最低支持 Android 4.1((API level 14)或更高版本的设备。在 app 下的 build.gradle 添加以下依赖就可以使用 Data binding

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

入门

  1. 首先定义一个实体类 User
data class User(val firstName: String?, val lastName: String, val age: Int) {
}
  1. 接下来在布局中,把 User 类属性 firstName 绑定到一个 TextView 上
<?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.i3kmsteady.databindingsample.entity.User" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="Hello Databinding !" />

    </android.support.constraint.ConstraintLayout>
</layout>
  • 选中根布局,使用 Alt + Enter 键,可以迅速的将一个普通布局,转变为一个 data binding 布局。以 layout 标签开头,后面跟 data 元素 和 视图元素。
    可以在 data 元素中中定义变量,如 user,接下来在视图元素中就可以通过 @{} 引用这个变量。

在 xml 中,推荐使用 tools:text="默认值" 的方式进行布局调试

  1. 在 Activity 中进行数据的绑定
  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityBasicBinding = DataBindingUtil.setContentView(this, R.layout.activity_basic)
        binding.user = User("steady", "13km", 24)
    }

默认情况下,Data binding 根据布局文件的名称再加上 binding 后缀生成绑定类,通过绑定类给给在 data 中声明的变量赋值即可

Data binding 表达式语言

  1. 可以在表达式中使用以下操作符和关键字
Mathematical + - / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + - ! ~
Shift >> >>> <<
Comparison == > < >= <=
instanceof
Grouping ()
Literals - character, String, numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:

示例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
  1. 不支持的操作符
this
super
new

表达式的基础使用

  1. 使用 < 操作符,要使用转义符 &lt;
 <?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>
        <import type="android.view.View"/>
        <variable name="user" type="com.i3kmsteady.databindingsample.entity.User"/>
    </data>
    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".ui.ExpressionLanguageActivity">

        <!-- &lt; 表示:"<", &gt; 表示:">" -->
        <android.support.v7.widget.AppCompatImageView
                android:visibility="@{user.age &lt;25 ? View.VISIBLE: View.GONE}"
                android:src="@mipmap/ic_launcher"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"/>

    </android.support.constraint.ConstraintLayout>
</layout>

否则会出现以下错误

> Error: 与元素类型 "android.support.v7.widget.AppCompatImageView" 相关联的 "android:visibility" 属性值不能包含 '<' 字符

除了 java.lang.* 包会自动导入,其他类都需要在 data 元素中手动导入。比如在表达式中根据条件去判断 View 的隐藏或显示 ,没有手动导入 View 包,会出现以下错误:

****/ data binding error ****msg:Identifiers must have user defined types from the XML file. View is missing it
file:G:\WYJ\test\DatabindingSample\app\src\main\res\layout\activity_expressionlanguage.xml
loc:13:49 - 13:52
  1. 空安全操作符 ??
        <!-- ?? 空安全操作符 -->
        <android.support.v7.widget.AppCompatTextView
            android:id="@+id/tvNull"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@{user.lastName ?? user.firstName}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/imageView"
            tools:text="空安全操作符" />

如果表达式左侧的值为 null,则显示右侧的值。生成的绑定类,会自动检查空值并避免空指针异常

  1. 资源引用

Data binding 表达式,也提供了对资源文件的引用。

  • 定义一个字符串资源
<resources>
    <string name="source_reference">My name is %1$s</string>
</resources>
  • xml 中通过表达式去引用

        <!-- 资源引用 -->
        <android.support.v7.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@{@string/source_reference(user.firstName)}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvFixed"
            tools:text="资源引用" />
  1. import
  • data 元素中,手动导入 View 类或者其他,就可以在表达式中引用导入的类。如通过表达式去控制 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">
      
          <data>
      
              <import type="android.view.View" />
      
              <import type="com.i3kmsteady.databindingsample.entity.User" />
      
              <variable
                  name="user"
                  type="User" />
          </data>
      
          <android.support.constraint.ConstraintLayout
              android:layout_width="match_parent"
              android:layout_height="match_parent">
      
              <android.support.v7.widget.AppCompatImageView
                  android:id="@+id/imageView"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="10dp"
                  android:src="@mipmap/ic_launcher"
                  android:visibility="@{user.age &lt; 13 ? View.VISIBLE:View.GONE}"
                  app:layout_constraintLeft_toLeftOf="parent"
                  app:layout_constraintRight_toRightOf="parent"
                  app:layout_constraintTop_toTopOf="parent" />
          </android.support.constraint.ConstraintLayout>
      </layout>
    
  • 在表达式中,也可以很方便的使用工具类来处理一些逻辑
    fun toUpperCase(str:String):String{
        return str.toUpperCase()
    }
    
    <?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.i3kmsteady.databindingsample.utils.MyStringUtilsKt" />
    
            <import type="com.i3kmsteady.databindingsample.entity.User" />
    
            <variable
                name="user"
                type="User" />
        </data>
    
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <!-- 使用工具类  -->
            <android.support.v7.widget.AppCompatTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="@{MyStringUtilsKt.toUpperCase(user.firstName)}"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tvResourceReference"
                tools:text="使用工具类" />
    
    
        </android.support.constraint.ConstraintLayout>
    </layout>
    
  1. 别名

    当导入的类,名称发生冲突时,可以使用别名来代替

    <import type="android.view.View"/>
    <import type="com.example.real.estate.View"
        alias="Vista"/>
    

    在表达式中,就可以使用 Vista 来引用自己定义的类

  2. Includes

    1. 定义一个 include 布局
      <?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.i3kmsteady.databindingsample.entity.User" />
          </data>
      
          <android.support.constraint.ConstraintLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
      
              <android.support.v7.widget.AppCompatTextView
                  android:layout_width="0dp"
                  android:layout_height="30dp"
                  android:background="@color/colorPrimary"
                  android:gravity="center"
                  android:text="@{user.firstName}"
                  android:textColor="@android:color/white"
                  app:layout_constraintLeft_toLeftOf="parent"
                  app:layout_constraintRight_toRightOf="parent"
                  tools:text="Include " />
          </android.support.constraint.ConstraintLayout>
      </layout>
      
    2. 通过 include 元素引用
      <!-- Include -->
      <include
          layout="@layout/layout_include"
          android:layout_width="0dp"
          android:layout_height="30dp"
          android:layout_marginTop="10dp"
          app:layout_constraintLeft_toLeftOf="parent"
          app:layout_constraintRight_toRightOf="parent"
          app:layout_constraintTop_toBottomOf="@+id/tvUtils"
          app:user="@{user}" />
      
    3. 需要注意的是,Data binding 不支持 include 作为 merge 元素的直接子元素。如:
      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:bind="http://schemas.android.com/apk/res-auto">
         <data>
             <variable name="user" type="com.example.User"/>
         </data>
         <merge><!-- Doesn't work -->
             <include layout="@layout/name"
                 bind:user="@{user}"/>
             <include layout="@layout/contact"
                 bind:user="@{user}"/>
         </merge>
      </layout>
      

笔者在使用 include 时,曾遇到一个小问题。我把界面 finish() 定义在基类中,但传入的是子类对象。在 include 布局中,声明导入的类型是基类的。在绑定 include 标签时,需要做一次强转,否则编译不通过。

事件绑定

Data binding 允许在表达式中,处理 View 的一些事件(如: onClick())。提供了两种方式来处理

  • Method references
  1. 首先定义 MyHandlers

    class MyHandlers {
    
       fun onClickFriend(view: View) {
           Toast.makeText(view.context, "方法引用", Toast.LENGTH_LONG).show()
       }
    
    }
    
  2. 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="user"
                type="com.i3kmsteady.databindingsample.entity.User" />
    
            <variable
                name="handlers"
                type="com.i3kmsteady.databindingsample.MyHandlers" />
        </data>
    
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".ui.EventActivity">
    
            <Button
                android:id="@+id/btnMethodReferences"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="@{handlers::onClickFriend}"
                android:text="@{user.firstName}"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent" />
        </android.support.constraint.ConstraintLayout>
    </layout>
    
  3. 当点击 Button 的时候,就可以正常的显示 Toast 了。关于 Method references,会在编译期间进行参数的检查, 如果监听器的参数和表达式调用的方法参数不一致,编译将会出错

    Listener class android.view.View.OnClickListener with method onClick did not match signature of any method handlers::onClickFriend
    
  • Listener bindings

Listener bindings 是在事件发生时,执行对应的表达式。和 Method references 不同的是,可以运行任意的表达式,对参数没有要求。

  1. 定义 Presenter
    class Presenter(val context: Context) {
    
        fun onSaveClick(task: Task) {
            Toast.makeText(context, task.toString(), Toast.LENGTH_LONG).show()
        }
    }
    
  2. 布局引用
    <?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.i3kmsteady.databindingsample.entity.User" />
    
            <variable
                name="task"
                type="com.i3kmsteady.databindingsample.entity.Task" />
    
            <variable
                name="presenter"
                type="com.i3kmsteady.databindingsample.Presenter" />
        </data>
    
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".ui.EventActivity">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:onClick="@{() -> presenter.onSaveClick(task)}"
                android:text="@{user.lastName}"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/btnMethodReferences" />
        </android.support.constraint.ConstraintLayout>
    </layout>
    
  3. 相比于 Method referencesListener bindings 的处理更加灵活,方法也不受参数的限制。

官方建议在 xml 中,表达式应该简练,避免复杂的逻辑操作。推荐在表达式的回调中,去处理复杂的逻辑。

Observable

Data binding 支持当一个对象的属性发生改变,自动去刷新 UI 。下面简单介绍下 BaseObservable 的用法

  1. 定义一个类,继承自 BaseObservable,在 get 方法上添加 @Bindable 注解。

        class Person : BaseObservable() {
        
            @get:Bindable
            var firstName: String = ""
                set(value) {
                    field = value
                    notifyPropertyChanged(BR.firstName)
                }
        
            @get:Bindable
            var lastName: String = ""
                set(value) {
                    field = value
                    notifyPropertyChanged(BR.firstName)
                }
        }
    
  2. 通过点击按钮,去修改 Person 的 firstName 属性值

     class Presenter(val context: Context) {
     
         fun onSaveClick(task: Task) {
             Toast.makeText(context, task.toString(), Toast.LENGTH_LONG).show()
         }
     
         fun updateName(person: Person) {
             person.firstName = "Kotlin: " + Random.nextInt()
         }
     }
    
  3. 在 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="presenter"
                type="com.i3kmsteady.databindingsample.event.Presenter" />
            
            <variable
                name="person"
                type="com.i3kmsteady.databindingsample.entity.Person" />
        </data>
        
        <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".ui.ObservableActivity">
            
            <android.support.v7.widget.AppCompatTextView
                android:id="@+id/tvName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="@{person.firstName}"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="默认值" />
            
            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:onClick="@{() -> presenter.updateName(person)}"
                android:text="@string/update_name"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tvName" />
        
        </android.support.constraint.ConstraintLayout>
    </layout>
    
  4. 当点击按钮的时候,可以看到界面上的文字发生变化。Data binding 会生成一个 BR 类,通过这个类可以引用绑定的数据资源。

@BindingAdapters 使用

Data binding 也提供了自定义逻辑的操作,通过 @BindingAdapter 注解即可实现。并且官方文档也建议,xml 中的表达式应当保持简洁,通过回调去处理复杂的逻辑。下面来看个示例:

  1. 首先定义一个方法,用 @BindingAdapter 注解
    @BindingAdapter("isGone")
    fun bindIsGone(view: View, age: Int) {
        if (age > 13) {
            view.visibility = View.VISIBLE
        } else {
            view.visibility = View.GONE
        }
    }
    

括号中的 isGone 表示的是在 xml ,可以使用的属性名称

  1. xml 中 使用
    <?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="age" type="Integer"/>
        </data>
    
        <android.support.constraint.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".ui.BindingAdaptersActivity">
    
            <android.support.v7.widget.AppCompatImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/ic_launcher"
                    app:isGone="@{age}"/>
    
        </android.support.constraint.ConstraintLayout>
    </layout>
    

data 元素中定义的 age 变量和类型,对应的是 bindIsGone() 的参数名称和类型。通过自定义属性,可以很方便的去处理更复杂的逻辑操作,使得 xml 中的代码变得简洁和容易维护。

示例代码已上传 GitHub,地址
参考资料:官方文档

相关文章

网友评论

      本文标题:Data binding 基础使用

      本文链接:https://www.haomeiwen.com/subject/ortjqqtx.html