美文网首页
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