DataBinding基础(一)

149 阅读9分钟

常见架构模式

  • MVC:

    • 在2011 2012时期广泛使用
    • 为什么是MVC模式

      • 由工程结构决定:

        • res:作View 层,
        • src中的Activity作控制层,所以是MVC架构
    • 缺点:

      • 承担View,控制,模型
      • 过于臃肿
      • 没有单一职责
      • 耦合度高
      • 扩展性不好
      • 管理混乱
  • MVP:Activity只做View层,引入P层(Presenter)

    • 特点:分层清晰

    • 实现过程:View->P->View:

      • 实例:

        • 做登录功能,用户点击按钮后,Activity将信息提交给P层,P层在提交给服务器,然后将服务器返回的信息回馈给View层(Activity)接口回调
    • 弊端:

      • 接口地狱:凡是P层的模型数据需要返回给View层均通过接口回调
  • MVVM:Activity:View层;P层换成VM层(ViewModel层):VM层实现ViewModel会更好用,不实现也不存在

    • 引用DataBinding实现V层与VM层的双向绑定

      • 这是两条单向的路:点击V层会触发VM层;修改VM层会影响V层
    • 问题:DataBinding属于MVVM上?

      • DataBinding是一个工具集,独立于架构模式;
      • 只不过大部分情况下,MVVM都会使用DataBinding;小部分情况下MVP也可以使用DataBinding;但是MVC基本就不用
      • DataBinding比MVVM出现得早
    • JetPack组件库中的ViewModel和MVVM中的VM层是两回事

      • 前者是组件库,是工具集;
      • 后者是架构之一
    • 那个设计模式最好?

      • 没有最好,只有适合;

      • MVC:适合与小项目,一两个人的,在Activity中能处理好就行

      • MVP:项目太大(编译就有8分钟),需要分层非常清晰(开发的人太多了)

      • MVVM:界面更新频繁(网易云音乐,腾讯视频)

        • 可以启用DataBinding+ViewModel+LiveData+LifeCycle实现数据驱动开发

DataBinding_Java版本实现数据驱动UI

  • 设计思路:实现数据驱动UI

    • 分层:

      • Activity作为控制层
      • JavaBean作为Model层
      • 布局文件作为View层
    • 实现效果:在Activity中开启对JavaBean属性修改,同时界面更新

  • 环境准备:

    • 打开dataBinding开关:两种打开方式

      image-20211218145222284

  • 工程结构:

    image-20211218160634554

  • 编写Model层(JavaBean):User类

    • 继承BaseObservable类:这个类就拥有DataBinding的能力了

       public class User extends BaseObservable
      
    • 编写字段:待会就更新这个

       private String name;
       private String pwd;
      
    • 添加对应的get/set方法

      • 在get方法上添加注解:@Bindable(这样子要规范一点)

        • 在BR文件中生成对应属性的数值标记:用于一个整数值指定

        图片.png

        • 在User中进行BR文件的引用:

        图片.png

      • 在set方法内添加:notifyPropertyChanged

        • 会自动生成BR文件(但是要等编译了才能用)
        • APT技术:字节码插桩
    • 完整代码:User

       package com.derry.databinding_java;
       ​
       import androidx.databinding.BaseObservable;
       import androidx.databinding.Bindable;
       ​
       // Model层
       public class User extends BaseObservable {
       ​
           private String name;
           private String pwd;
       ​
           public User(String name, String pwd) {
               this.name = name;
               this.pwd = pwd;
           }
       ​
           @Bindable // BR里面标记生成 name数值标记
           public String getName() {
               return name;
           }
       ​
           public void setName(String name) {
               this.name = name;
               notifyPropertyChanged(BR.name); // APT又是主接处理器技术 BR文件,数据驱动UI
           }
       ​
           @Bindable // BR里面标记生成 pwd数值标记
           public String getPwd() {
               return pwd;
           }
       ​
           public void setPwd(String pwd) {
               this.pwd = pwd;
               notifyPropertyChanged(BR.pwd); // APT又是主接处理器技术 BR文件
           }
       }
      
  • 使用DataBinding管理布局:管理布局中所有的控件

    • 使用:将鼠标悬停在activity_main.xml文件的最外标签上,第一个提示

      • 将布局文件分为两半:

        • 不拆,会崩掉的
        • Android系统是不认识的;
      • 拆开后会增加一个tag的

        image-20211218154904262

        • 上面一半是拿给DataBinding使用的,Android是无法解析的

          • 动态注入了Tag:寻找控件
          • 其实上面由activity_main.xml上一半生成的,但是也会有xml下面的
        • 下面一半,用于Android解析,绘制View使用的

      • activity_main.xml布局拆分后的位置

        • 上面一半:交给DataBinding进行布局管理的,这部分Android是不认识的

      • 下面一半在这里:

        image-20211218162047693

    • 布局文件代码展示:拆分后的activity_main.xml

       <?xml version="1.0" encoding="utf-8"?>
       ​
       <!-- layout是DataBinding 管理了 我们整个布局了 -->
       <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>
               <!-- 不要被迷惑了,不是反射 -->
               <!-- setUser函数  setAa -->
               <variable
                   name="user"
                   type="com.derry.databinding_java.User" />
           </data>
           <!-- 上面的是 DataBinding 内部用的, 注意:Android View体系 不认识  -->
       ​
           <!-- Android View体系的
                下面的所有内容, 拿给Android绘制
            -->
           <LinearLayout
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:orientation="vertical">
       ​
               <!-- @=  View -> Model  一向 -->
               <!-- EditText -->
               <EditText
                   android:id="@+id/tv1"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="@={user.name}"
                   android:textSize="50sp" />
       ​
               <EditText
                   android:id="@+id/tv2"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="@={user.pwd}"
                   android:textSize="50sp" />
           </LinearLayout>
       ​
       </layout>
      
  • 编写控制层:MainActivity(使用DataBinding技术奖Model层与View层进行绑定)

    • 实现效果:在子线程中,更新JavaBean(Model层)的属性,在View层(界面上可以看到)

    • 实现细节:

      • 加载布局文件activity_main.xml

         final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        
        • 跟ViewModel类似:

          • 以布局为对象,自动生成一个类:ActivityMainBinding
          • 点击这个类(CTRL+鼠标左键)就会跳转到对应的布局文件:activity_main.xml
      • 建立绑定关系:

         binding.setUser(user); // 必须要建立绑定关系,否则没有任何效果
        
      • 子线程更新数据

         new Thread(new Runnable() {
             @Override
             public void run() {
                 for (int i = 0; i < 5; i++) {
                     try {
                         Thread.sleep(1000);
                         user.setName(user.getName() + "第"+i+"次"); // view.setText(text);
                         user.setPwd(user.getPwd() + "第"+i+"次");
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         }).start();
        
    • 完整代码:MainActivity

       package com.derry.databinding_java;
       ​
       import androidx.appcompat.app.AppCompatActivity;
       import androidx.databinding.DataBindingUtil;
       ​
       import android.os.Bundle;
       ​
       import com.derry.databinding_java.databinding.ActivityMainBinding;
       ​
       //Activity作控制
       public class MainActivity extends AppCompatActivity {
       ​
           User user;
       ​
           @Override
           protected void onCreate(Bundle savedInstanceState) {
               super.onCreate(savedInstanceState);
               // setContentView(R.layout.activity_main);
       ​
               final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
       ​
               user = new User("用户名更新:", "密码更新:");
               binding.setUser(user); // 必须要建立绑定关系,否则没有任何效果
       ​
       ​
               // Model(修改Model的数据)  ---> View(UI的控件就DataBinding自动刷新)  一向 更新数据驱动UI
               new Thread(new Runnable() {
                   @Override
                   public void run() {
                       for (int i = 0; i < 5; i++) {
                           try {
                               Thread.sleep(1000);
                               //Model--->View:数据驱动UI
                               user.setName(user.getName() + "第"+i+"次"); // view.setText(text);
                               user.setPwd(user.getPwd() + "第"+i+"次");
                           } catch (InterruptedException e) {
                               e.printStackTrace();
                           }
                       }
                   }
               }).start();
       ​
       //        user.setName("888");
           }
       }
      
  • 运行效果:每间隔一秒,界面刷新一次

    image-20211218163244614

图片.png

  • 错误排查:

    • 查布局:

      • DataBinding使用APT技术会对布局进行全盘扫描,布局出错,处处报错
    • 全盘加载

      • 这个就很坑,有些时候DataBinding就是加载不出来,但是重启AS就好了
    • 需要编译一下才行

  • DataBinding的弊端

    • 之前是很卡,并且项目布局越大,越卡(宁愿用MVP陷入接口地狱,也不用DataBinding)

DataBinding实现双向绑定(Kotlin 版本)先实现数据驱动UI

  • 分层:

    • model层:StudentInfo
    • 控制层:MainActivity
    • View层:activity_main.xml
  • 工程结构:

    image-20211218165512357

  • 编写model层:StudentInfo

    • 仿照java版本出错

      • 继承BaseObservable类

      • 为get添加注解

        • Kotlin中为每个属性都是内置了get/set方法的,因此重写get,然后添加注解,发现报错:注解是给函数和方法的,此时的get就只是一个表达式而已,这个注解就失效了,无法生成BR中的字段

          image-20211218170027325

      • 尝试向set方法内添加:notifyPropertyChanged(BR.name)

        • 还是不行的,注解都加不上去,BR文件都无法生成
    • Kotlin版本中的model层的正确编写

       package com.derry.databinding_kt.model
       ​
       import androidx.databinding.BaseObservable
       import androidx.databinding.Bindable
       import androidx.databinding.ObservableField
       ​
       // DataBinding BindingAdapter 涉及到判空 后面看
       ​
       class StudentInfo /*: BaseObservable()*/ {
       ​
           // 第一种方式  未修复  KT放弃第一种方式
           var name: String? = null
           //@Bindable//注解是给函数和方法的,此时的get就只是一个表达式而已,这个注解就失效了,无法生成BR中的字段
           get() {
               return field
           }
           set(value) {
               field = value
               // notifyPropertyChanged(BR.name)
           }
       ​
           var pwd : String? = null
               get() {
                   return field
               }
               set(value) {
                   field = value
               }
       ​
           // 第二种方式  已修复  懒加载
           val nameF : ObservableField<String> by lazy { ObservableField<String>() }
           val pwdF : ObservableField<String> by lazy { ObservableField<String>() }
       }
      
  • View 层编写:将布局交由DataBinding进行管理

    • 代码实现:

       <?xml version="1.0" encoding="utf-8"?>
       <!-- DataBinding的编译标准 -->
       <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">
       ​
           <!-- 定义该View(布局)需要绑定的数据来源,拿给下面的那个用 -->
           <data>
               <variable
                   name="studentInfo"
                   type="com.derry.databinding_kt.model.StudentInfo" />
           </data>
       ​
       ​
       ​
           <!-- 安卓认识的 常规布局 -->
           <!-- 布局常规代码如下 -->
           <LinearLayout
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               tools:context=".MainActivity"
               android:orientation="vertical">
       ​
               <!-- 未修复 -->
               <EditText
                   android:id="@+id/et_name1"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:text="@{studentInfo.name}"
                   />
       ​
               <EditText
                   android:id="@+id/et_pwd1"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:text="@{studentInfo.pwd}"
                   android:layout_marginBottom="60dp"
                   />
       ​
       ​
       ​
               <!-- 修复后 -->
               <EditText
                   android:id="@+id/et_name2"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:text="@{studentInfo.nameF}"
                   />
       ​
               <EditText
                   android:id="@+id/et_pwd2"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:text="@{studentInfo.pwdF}"
                   android:layout_marginBottom="60dp"
                   />
       </layout>
      
  • 编写控制层:MainActivity

    • 加载布局文件

       val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
      
      • 细节一:DataBindingUtil

        • 这个DataBindingUtils是系统自动生成的(但是要编译一下才行,因为其背后使用了APT技术)
      • 细节二:指定了泛型< ActivityMainBinding >

        • 查看源码:

          image-20211218170653335

        • 限定泛型类型:ViewDataBinding的子类(可以是ActivityMainBinding)

          image-20211218170948077

        • 为什么java版本中,在加载布局文件时,不需要指定泛型?

          • 因为java没有类型推导机制,java在声明变量的时候就已经决定了变量的类型了

            image-20211218171308161

          • kotlin中变量的类型时依托于其内在的类型推导机制,从而确定的;如果不指定泛型,那么在kotlin中这个变量是什么都不知道

    • 子线程更新model层中的数据:三秒后更新

       Log.d(TAG, "name:${studentInfo.name}, pwd:${studentInfo.pwd}")
       Handler().postDelayed({
           studentInfo.name = "第一种更新方式,更新用户名"
           studentInfo.pwd = "第一种更新方式,更新密码"
       }, 3000)
       ​
       // 修复后:  Model ---> View  一向
       Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}")
       Handler().postDelayed({
           studentInfo.nameF.set("第二种更新方式,更新用户名")
           studentInfo.pwdF.set("第二种更新方式,更新密码")
       }, 3000)
      
    • 数据传递流程:

      • 在控制层(Activity)修改Model层(StudentInfo)数据

        image-20211218173512944

      • 通过Model层

        image-20211218173559011

      • 反馈到View层(activity_main.xml)中的由DataBinding管理的(activity_main.xml的上一半)中的data标签中

        image-20211218173639231

      • View层再从这个data标签里面去拿数据,实际上拿的就是Model层中的数据

        image-20211218173741021

    • 完整代码:

       package com.derry.databinding_kt
       ​
       import androidx.appcompat.app.AppCompatActivity
       import android.os.Bundle
       import android.os.Handler
       import android.util.Log
       import androidx.databinding.DataBindingUtil
       import com.derry.databinding_kt.databinding.ActivityMainBinding
       import com.derry.databinding_kt.model.StudentInfo
       ​
       class MainActivity : AppCompatActivity() {
       ​
           private val TAG = MainActivity::class.java.simpleName
       ​
           private val studentInfo = StudentInfo()//拿到model数据了
       ​
           override fun onCreate(savedInstanceState: Bundle?) {
               super.onCreate(savedInstanceState)
               // setContentView(R.layout.activity_main)
       ​
               val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
       ​
               // TODO 单向绑定第一种方式:<Model ---> View>
               studentInfo.name = "初始化用户名"
               studentInfo.pwd = "初始化密码"
               binding.studentInfo = studentInfo
       ​
               // 数据的刷新 发现界面没有效果,目前:界面不会根据Model的变化 而 变化,没有加注解的,根本就无法工作的
               // 未修复:
               Log.d(TAG, "name:${studentInfo.name}, pwd:${studentInfo.pwd}")
               Handler().postDelayed({
                   studentInfo.name = "第一种更新方式,更新用户名"
                   studentInfo.pwd = "第一种更新方式,更新密码"
               }, 3000)
       ​
               // 修复后:  Model ---> View  一向
               Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}")
               Handler().postDelayed({
                   studentInfo.nameF.set("第二种更新方式,更新用户名")
                   studentInfo.pwdF.set("第二种更新方式,更新密码")
               }, 3000)
       ​
           }
       }
      
  • 运行截图:

    • 初始状态:

    图片.png

    • 等待三秒更新UI

    图片.png

实现双向绑定

  • 概述:

    • Model--->View:数据驱动UI

      • 在控制层(Activity)中更新Model层(StudentInfo)View层(界面)显示更新后的数据
    • View--->Model:UI驱动数据

      • 在View层(界面)更新Model层(StudnetInfo)的数据,在控制层(Activity)中通过打印日志进行显示
  • 工程结构:

    图片.png

  • Model层:StudentInfo

     package com.derry.databinding_kt.model
     ​
     import androidx.databinding.BaseObservable
     import androidx.databinding.Bindable
     import androidx.databinding.ObservableField
     ​
     // DataBinding BindingAdapter 涉及到判空 后面看
     ​
     class StudentInfo /*: BaseObservable()*/ {
     ​
         // 第一种方式  未修复  KT放弃第一种方式
         var name: String? = null
         //@Bindable//注解是给函数和方法的,此时的get就只是一个表达式而已,这个注解就失效了,无法生成BR中的字段
         get() {
             return field
         }
         set(value) {
             field = value
             // notifyPropertyChanged(BR.name)
         }
     ​
         var pwd : String? = null
             get() {
                 return field
             }
             set(value) {
                 field = value
             }
     ​
     ​
     ​
     ​
     ​
     ​
         // 第二种方式  已修复  懒加载
         val nameF : ObservableField<String> by lazy { ObservableField<String>() }
         val pwdF : ObservableField<String> by lazy { ObservableField<String>() }
     }
    
  • 控制层:Activity

     package com.derry.databinding_kt
     ​
     import androidx.appcompat.app.AppCompatActivity
     import android.os.Bundle
     import android.os.Handler
     import android.util.Log
     import androidx.databinding.DataBindingUtil
     import com.derry.databinding_kt.databinding.ActivityMainBinding
     import com.derry.databinding_kt.model.StudentInfo
     ​
     class MainActivity : AppCompatActivity() {
     ​
         private val TAG = MainActivity::class.java.simpleName
     ​
         private val studentInfo = StudentInfo()//拿到model数据了
     ​
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             // setContentView(R.layout.activity_main)
     ​
             val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
     ​
             // TODO 单向绑定第一种方式:<Model ---> View>
             studentInfo.name = "初始化用户名"
             studentInfo.pwd = "初始化密码"
             binding.studentInfo = studentInfo
     ​
             // 数据的刷新 发现界面没有效果,目前:界面不会根据Model的变化 而 变化,没有加注解的,根本就无法工作的
             // 未修复:
             Log.d(TAG, "name:${studentInfo.name}, pwd:${studentInfo.pwd}")
             Handler().postDelayed({
                 studentInfo.name = "第一种更新方式,更新用户名"
                 studentInfo.pwd = "第一种更新方式,更新密码"
             }, 3000)
     ​
             // TODO 双向绑定(Model ---> View 一向        and          View ---> Model 一向 )  == 双向
     ​
             // Model ---> View 一向
             studentInfo.nameF.set("用户名初始化")
             studentInfo.pwdF.set("密码初始化") // 修改Model --> View
             binding.studentInfo = studentInfo
             Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}")
     ​
             Handler().postDelayed({
                 studentInfo.nameF.set("Model--->View")
                 studentInfo.pwdF.set("实现数据驱动UI")
             }, 3000)
     ​
             Handler().postDelayed({
                  // 10秒后,测试 View ---> Model    Model是否被修改了
                 Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}")
             }, 10000)
     ​
             // 我们开发用的多的是, Model(修改888) ---> View(888) 一向
         }
     }
    
  • View层:activity_main.xml

     <?xml version="1.0" encoding="utf-8"?>
     <!-- DataBinding的编译标准 -->
     <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">
     ​
         <!-- 定义该View(布局)需要绑定的数据来源,拿给下面的那个用 -->
         <data>
             <variable
                 name="studentInfo"
                 type="com.derry.databinding_kt.model.StudentInfo" />
         </data>
     ​
     ​
     ​
         <!-- 安卓认识的 常规布局 -->
         <!-- 布局常规代码如下 -->
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             tools:context=".MainActivity"
             android:orientation="vertical">
     ​
             <!-- 未修复 -->
             <EditText
                 android:id="@+id/et_name1"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@{studentInfo.name}"
                 />
     ​
             <EditText
                 android:id="@+id/et_pwd1"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@{studentInfo.pwd}"
                 android:layout_marginBottom="60dp"
                 />
     ​
     ​
     ​
             <!-- 修复后 -->
             <EditText
                 android:id="@+id/et_name2"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@{studentInfo.nameF}"
                 />
     ​
             <EditText
                 android:id="@+id/et_pwd2"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@{studentInfo.pwdF}"
                 android:layout_marginBottom="60dp"
                 />
     ​
     ​
             <!-- 双向绑定  = 就代表着View可以驱动Model @= View -> Model   View -> Model 一向 -->
             <EditText
                 android:id="@+id/et_name3"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
             <!--这个等号就是 实现UI驱动数据在界面上面修改数据后在Model层上就看到了-->
                 android:text="@={studentInfo.nameF}"
                 />
     ​
             <EditText
                 android:id="@+id/et_pwd3"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@={studentInfo.pwdF}"
                 android:layout_marginBottom="60dp"
                 />
     ​
     ​
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@={studentInfo.nameF}"
                 />
     ​
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@={studentInfo.pwdF}"
                 />
     ​
         </LinearLayout>
     </layout>
    
  • 运行截图:

    • 界面初始化

      图片.png

    • 等待三秒,UI更新:数据驱动UI

      图片.png

    • 改变View层值

      图片.png

    • 在控制层中打印日志信息:View--->Model

      图片.png

  • 总结:

    • 在开发的时候一般都是单向的(View--->Model),因为双向绑定非常耗费性能

    • Databinding还有很多的好东西

      • BindingAdapter等

      • Model--->View:搞个事件就行了

问题

  • Android Studio无法输入中文

    • 关掉,然后重启,就行了
  • Android Studio格式化代码:代码是一行显示的

    • CTRL+ALT+L
  • xml引用判空:DataBinding中的BindingAdapter

    image-20211218165047955