【Android】浅析DataBinding

简介

DataBinding是一个实现数据和UI绑定的框架,是实现MVVM模式的工具。在使用DataBinding之前我们不可避免地要编写大量的毫无营养的代码,如 findViewById()、 setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等,通过 Data Binding , 我们可 以通过声明式布局以精简的代码来绑定应用程序逻辑。

缺点:

  1. 灵活性较低
  2. Model发生变化时,ViewDatabinding采用异步更新数据,对于现实大量数据的ListView,会有一定延迟,效率太低
  3. 自动生成大量代码和属性字段:ViewDataBinding 实现类 DataBinderMapper 等
  4. ViewModel与View一一对应

使用步骤

  1. 配置环境

    在build.gradle文件中设置

    java 复制代码
    dataBinding{
            enabled=true
        }
  2. 数据绑定布局文件

    先写出一个Model实体类

    java 复制代码
    public class User {
        private String username;
        private int age;
        public User(String username,int age){
            this.username=username;
            this.age=age;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public int getAge() {
            return age;
        }
    }

    数据绑定的布局文件和常用的不同,从布局根标记开始,后面依次是数据元素和视图根元素

    控件要使用绑定的数据通过@{}(单向绑定)@={}(双向绑定)实现

    xml 复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        >
        <data>
            <!-- 数据对象,name 是变量名,type 是类的全路径 -->
            <variable
                name="user"
                type="com.example.mvvmtext.model.User" />
            <variable
                name="handler"
                type="com.example.mvvmtext.MainActivity.Handler" />
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:gravity="center">
             <!-- 这里的 user.name 调用的实际上是 User 类中的 getName() 方法,如果没有对应的 get 方法,就会报错 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello World!"
                android:id="@+id/tv_show"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="30dp"
                android:textSize="20dp"
                android:text="@{user.username}"/>
              <!-- 我们知道 TextView 的内容必须是 String 类型的,这里传入 int 会报错
                 java.lang 包下的类不需要导入 -->
             <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="30dp"
                android:textSize="20dp"
                android:text="@{String.valueOf(user.age)}"/>
            
            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{()->handler.onClick()}"/>
            
        </LinearLayout>
        
    
    </layout>
  3. 获取binding对象

    java 复制代码
    /*
    使用DataBinging布局后这里需要DataBindingUtil来完成setContentView,返回值为布局的ViewBinding对象,这个类由DataBinding框架自动生成
    */
    ActivityDataBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_data);

    在Fragment中

    java 复制代码
    public class MainFragment extends Fragment {
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            //DataBinding 布局是可以使用下面两种方式,inflate 出来的。
    //        FragmentMainBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
            FragmentMainBinding binding = FragmentMainBinding.inflate(inflater, container, false);
            return binding.getRoot();
        }
    }

    还要在Activity/Fragment中给实体类赋值

    java 复制代码
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            EdgeToEdge.enable(this);
            ActivityDataBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_data);
            User user=new User("mm",28);
             //如果不传 user 或不给他某个参数赋值呢?
            //答:DataBinding不惧怕空指针异常,若表达式结果为null,则根据其结果的值类型显示不同,比如引用类型显示null,int类型显示0,string类型显示空
            binding.setUser(user);
            binding.setHandler(new Handler());
        }
        public class Handler{
            public void onClick(){
                Toast.makeText(MainActivity.this,"登录",Toast.LENGTH_SHORT).show();
            }
        }
    }
  4. 表达式的使用

    关键字

    可以在@{}中使用以下运算符和关键字

    • 运算类 + - / * % && || & | ^ ! ~ == > < >= <= () >> >>> <<
    • 字符串连接 + (注意字符串要用" "括起来)
    • instanceof
    • 文字 - 字符,字符串,数字, null
    • 强转 cast
    • 方法调用
    • res 资源访问
    • 数组访问 []
    • 三目运算 ?:
    • 合并运算 ?? ------选择左边的是 null 吗,不是选左边,如果是选右边

    不支持的操作

    • this
    • super
    • new
    • 显示泛型调用
  5. 事件绑定

    • 方法引用:事件可以直接绑定到处理程序方法,表达式在编译时处理,如果方法不存在或其签名不正确,会收到编译时错误

      在方法引用中,方法的参数必须与事件侦听器的参数匹配。

      编写一个事件

      java 复制代码
        public class Handler{
              public void onClick(){
                  Toast.makeText(MainActivity.this,"登录",Toast.LENGTH_SHORT).show();
              }
          }

      在layout文件中注册,并绑定控件

      xml 复制代码
         <data>
              <variable
                  name="user"
                  type="com.example.mvvmtext.model.User" />
              <variable
                  name="handler"
                  type="com.example.mvvmtext.MainActivity.Handler" />
          </data>
            <Button
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:onClick="@{handler::onClick}"/>

      最后在java代码中将事件绑定到布局

      java 复制代码
      binding.setHandler(new Handler());
    • 监听器绑定:发生事件时运行的绑定表达式,与方法引用类似,但允许运行任意数据绑定表达式。

      在监听器绑定中,返回值必须与监听器的期望返回值相匹配(除非它为void)

      编写一个事件

      java 复制代码
        public class Handler{
              public void onClick(){
                  Toast.makeText(MainActivity.this,"登录",Toast.LENGTH_SHORT).show();
              }
          }

      在layout文件中注册,并绑定控件

      xml 复制代码
         <data>
              <variable
                  name="user"
                  type="com.example.mvvmtext.model.User" />
              <variable
                  name="handler"
                  type="com.example.mvvmtext.MainActivity.Handler" />
          </data>
           <Button
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:onClick="@{()->handler.onClick()}"/>

      最后在java代码中将事件绑定到布局

      java 复制代码
      binding.setHandler(new Handler());

      如果想在表达式中使用参数,可以采用以下方式

      java 复制代码
         public void onClick1(View view,User user){
                  view.setBackgroundColor(Color.BLUE);
              }
      xml 复制代码
       <Button
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:onClick="@{(v)->handler.onClick1(v,user)}"/>

      如果正在监听的事件返回值不为void,则表达式也必须返回相同类型的值,如监听长按事件,返回值应为boolean

      java 复制代码
      public boolean onLongClick(View view, Task task){ ... }
      xml 复制代码
      android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

布局属性

  • import用法与别名

    data节点支持import用法,并且需要具体的类名

    xml 复制代码
     <import type="com.example.mvvmtext.model.User"/>
            <variable
                name="user"
                type="User" />

    如果import引用了两个相同的类名,我们可以用别名来区分它们

    xml 复制代码
    <import type="com.example.mvvmtext.model.User"/>
            <import type="com.example.mvvmtext.User"
                alias="Muser"/>
            <variable
                name="user"
                type="User" />
            <variable
                name="muser"
                type="Muser" />
  • 变量定义

    我们也可以在XML中定义一些基本数据类型和String类型,java.lang.*中的类会自动导入,无需使用import

    xml 复制代码
    <variable
                name="name"
                type="String" />
            <variable
                name="age"
                type="int" />
            <variable
                name="man"
                type="boolean" />

    除此之外,我们还可以定义List、Map等集合变量

    xml 复制代码
     <import type="java.util.ArrayList"/>
            <import type="java.util.Map"/>
            <variable
                name="map"
                type="Map&lt;String,String&gt;" />
            <variable
                name="list"
                type="ArrayList&lt;String" />
            <variable
                name="arrays"
                type="String[]" />
  • 自定义Binding类名

    可以通过修改布局文件中data节点的class属性,改变Binding类的类名和生成方式

    xml 复制代码
     //全类名方式,此时Binding类位于com.model包中,名称为ActivityFirst
    <data class="com.model.ActivityFirst">
        //仅改变类名,不改变生成位置
    <data class="ActivityFirst">
        //改变位置到项目的包的根目录下
    <data class=".ActivityFirst">   
  • 静态方法的调用

    在布局文件中也可以直接调用静态方法

    java 复制代码
     public class Util{
            public static String getName(User user){
                return user.getUsername();
            }
        }
    xml 复制代码
     <import type="com.example.mvvmtext.MainActivity.Util"/>
     <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="30dp"
                android:text="@{Util.getName(user)}"/>
  • Converter

    转换器,把数据格式转为需要的格式

    xml 复制代码
     <variable
                name="time"
                type="java.util.Date" />
     <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{time}"/>           

    text属性需要String类型的值,这里给的是Date类型的值,需要写一个Converter来进行类型转换

    java 复制代码
    public class Util{
            @BindingConversion
            public static String converDate(Date date){
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
                return sdf.format(date);
            }
        }

    coverDate在哪个类中不重要,重要的是要有@BindingConversion注解

动态更新和双向绑定

实现Model实体类的内容发生变化,UI动态更新

DataBinding提供了3种动态更新机制:ObservableField(字段)、Observable(类)、Observable(容器类)

结合动态更新机制,还可以实现双向绑定:当View发生改变时,ViewModel也会通知Model进行数据更新

(1)使用Observable

​ 通过继承BaseObservable实现动态更新

java 复制代码
public class ObUser extends BaseObservable {
    private String name;
    private String level;
    public ObUser(String name,String level){
        this.name=name;
        this.level=level;
    }
    //用@Bindable注解标记的getter会在BR中生成一个相应的字段
    @Bindable
    public String getName() {
        return name;
    }

    @Bindable
    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
        //通知系统BR.level字段发生了改变,并更新UI
        notifyPropertyChanged(BR.level);
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
}
java 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        ActivityMainBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
        ObUser obUser=new ObUser("mm","A");
        binding.setObuser(obUser);
        binding.btUpdataObuser.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                obUser.setName("太子");
            }
        });
    }
}
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="obuser"
            type="com.example.mvvmthree.model.ObUser" />
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{obuser.name}"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{obuser.level}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/bt_updata_obuser"/>

    </LinearLayout>
</layout>

(2)使用ObservableField

​ 除了集成BaseObservable实现动态更新,还可以使用系统为我们提供的所有基本数据类型对应的Onservable类,如OnservableInt、OnservableFloat等,也可以使用引用数据类型和基本数据类型都支持的ObservableField

java 复制代码
public class ObFUser {
    public ObservableField<String> name=new ObservableField<>();
    public ObservableField<String> level=new ObservableField<>();
    public ObFUser(String name,String level){
        this.level.set(level);
        this.name.set(name);
    }

    public void setName(ObservableField<String> name) {
        this.name = name;
    }

    public void setLevel(ObservableField<String> level) {
        this.level = level;
    }

    public ObservableField<String> getLevel() {
        return level;
    }

    public ObservableField<String> getName() {
        return name;
    }
}
java 复制代码
ObFUser obFUser=new ObFUser("风","t");
        binding.setObfuser(obFUser);
        binding.btUpdataObfuser.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                obFUser.name.set("梅");
            }
        });

(3)使用Observable容器类

​ 适合有多个同类型的数据需要动态更新的情况,包括ObservableArrayList和ObservableArrayMap

java 复制代码
 ObservableArrayList<User> list=new ObservableArrayList<>();
        User user1=new User("朱","S");
        User user2=new User("凤","P");
        list.add(user1);
        list.add(user2);
        binding.setList(list);
        binding.btUpdataUser.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                user1.setName("echo");
                user2.setName("花");
            }
        });
xml 复制代码
 <data>
        <import type="androidx.databinding.ObservableArrayList"/>
        <import type="com.example.mvvmthree.model.User"/>
        <variable
            name="obuser"
            type="com.example.mvvmthree.model.ObUser" />
        <variable
            name="obfuser"
            type="com.example.mvvmthree.model.ObFUser" />
        <variable
            name="list"
            type="ObservableArrayList&lt;User&gt;" />
    </data>

(4)双向绑定

xml 复制代码
 <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{obuser.name}"
            />
        //实现双向绑定
      <EditText
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@={obuser.name}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/bt_updata_obuser"/>
java 复制代码
ActivityMainBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
        ObUser obUser=new ObUser("mm","A");
        binding.setObuser(obUser);
        binding.btUpdataObuser.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                obUser.setName("太子");
            }
        });
 
相关推荐
爱学习的小可爱卢2 小时前
编程语言30年:从Java到Rust的进化史
java·开发语言·rust
一个很帅的帅哥2 小时前
three.js和WebGL
开发语言·javascript·webgl
一 乐2 小时前
校园社区系统|基于java+vue的校园悬赏任务平台系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
吗~喽2 小时前
【C++】模板进阶
c语言·开发语言·c++
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Python爬虫的二手房信息爬取及分析为例,包含答辩的问题和答案
开发语言·爬虫·python
layman05282 小时前
在python中受限于GIL,进程中只允许一个线程处于允许状态,多线程无法充分利用CPU多核
开发语言·python
捧 花2 小时前
Go Web 开发流程
开发语言·后端·golang·restful·web·分层设计
南猿北者2 小时前
go语言基础语法
开发语言·后端·golang
CC.GG2 小时前
【Qt】Qt初识
开发语言·qt