美文网首页
DataBinding 开发知识汇总

DataBinding 开发知识汇总

作者: 王朋6 | 来源:发表于2018-08-01 10:31 被阅读0次

    https://github.com/LaxusJie/DataBindingSimple

    Data binding 入坑笔记二进阶篇之双向绑定

    https://www.cnblogs.com/longjunhao/p/5860353.html

    https://academy.realm.io/cn/posts/data-binding-android-boyar-mount/ 英文翻译(较全)

    https://www.colabug.com/1197309.html  使用@InverseBindingMethods实现反向绑定

    https://blog.csdn.net/zhangphil/article/details/77839555  使用@InverseBindingAdapter反向绑定

    DataBinding框架使用的的环境要求:

    1、保证接入的项目gradle插件版本不低于1.5.0-alpha1:

    classpath'com.android.tools.build:gradle:1.5.0'

    2、在对应模块(Module)的build.gradle文件添加如下配置

    dataBinding {  

         enabledtrue

    }

    Databinding接入

    一、布局文件

    要使用Data Binding首先xml文件有了以下变化,他的根节点变成了layout,里面包含一个data节点和原根节点LinearLayout,其中这个data是用来绑定数据的。

    在data内可以利用variable声明变量,里面有两个属性name和type,name是变量的名字,type是变量的类型。(这里的User是一个实体类,下面说) 

    也可以把类型提出来用import导入 ,基础类型(java.lang包下的,比如String)可以直接使用,无需import。

    配置好布局文件后,IDE会根据xml文件的名称自动生成一个DataBinding类用于数据绑定,命名规则如下

    activity_main.xml-> ActivityMainBinding

    如果不喜欢自动生成的Data Binding名,我们可以自己来定义

        .........

    class对应的就是生成的Data Binding名

    二、(viewModle)POJO对象

    POJO类对象User,这种类型的对象具有从不改变的数据。在应用程序中,数据是一次读取,此后从不更改,这是很常见的。

    public class User{

      public final String firstName;

      public final String lastName;

      public User(String firstName, String lastName) {

          this.firstName = firstName;

          this.lastName = lastName; 

         }

    }

    也可以使用JavaBean形式

    public class User{

    private final String firstName;

    private final String lastName;

    public User(String firstName, String lastName) {

     this.firstName = firstName;

     this.lastName = lastName; 

     }

      public String getFirstName() {

           returnthis.firstName; 

     }

      public String getLastName() {

       returnthis.lastName;

      }

    }

    修改Activity中的onCreate方法,

    用DataBindingUtil.setContentView替代原来的setContentView方法从而实现数据绑定,创建user对象,

    通过binding.setUser(user)从而实现数据填充。(此时还是自动填充,需要手动调用binding对象的setUser方法通binding对象更新view视图)

    private User user;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState); 

           ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);     

           user =newUser("Laxus","J"); 

           binding.setUser(user);  

      }

    此处可以多说一句,binding的setUser()方法是根据布局文件中标签在编译时生成了一个方法,binding也是编译时生成的一个类,这个方法就声明在binding这个类中,并且还有对应的一个getUser方法。除此之外,binging里还有int age这个属性的set,get方法;

    前面说了,写到这个一步已经可以将user对象的数据刷新到布局视图中去了,但是需要手动调用setUser方法,那么怎样才能在数据更新后自动更新到布局中而不用调用setUser方法呢?

    Databing提供了一个Observable接口,实现此接口的的pojo类可以被Databing框架识别和管理;为了方便,Databing框架已经为我们封装了一个BaseObservable类,里面帮我们已经封装了好了基础功能;

    让一个类继承BaseObservable:

    public class Student extends BaseObservable{

      private String name;

      privateString className;

      @Bindable

      public String getName() {

        return  name;  

       }

    @Bindable

    public String getClassName() {

    return className;   

     }

    public void setName(String name) {

    this.name = name;

    notifyPropertyChanged(BR.name);

     }

    public void setClassName(String className) {

        this.className = className;     

       notifyPropertyChanged(BR.className);  

      }

    }

    BR是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable标记过 getter方法会在BR中生成一个静态常量池,所有Pojo类的属性在这个BR都有唯一一个对应的BR.XX常量。

    public class BR{

    public  static  final int_all =0;

    public  static  final  int className =1

    }

    在setter方法里我们需要用notifyPropertyChanged方法来更新对象。写了这个notifyPropertyChanged方法,此时就可以不用之后再改变一个对象中的某一个属性,就可以不用再调用binding对象的整个set方法;

    还有另一种适用于POJO的写法,这种写法不需要继承BaseObservable,不过每个成员变量都要new一个ObservableField对象。

    public class Teacher{

    public final   ObservableField name =newObservableField<>();

     public  final  ObservableField className = newObservableField<>();

      public  final  ObservableInt age =newObservableInt();

    }

    注意,这个类里面没有set、get方法,不用写,Databing已经做了处理,使用时在代码中如下写:

    mTeacher.name.set("wang");

     mTeacher.className.set("Java");

    而在布局文件中的使用方法与普通属性写法一样;

    DataBinding  还准备了ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble, 以及ObservableParcelableObservableArrayMapObservableArrayList

    逆向绑定

    上边已经讲完了,数据改变引起视图改变的单向绑定,下面说一下页面中空间的内容改变如何引起数据改变;

    如果想在用户输入的时候给一个pojo类的属性赋值,则需要用到@={}这个操作符

    只比单向绑定时,在@和{}中间多了一个=,就变成了双向绑定!

    引申

    我们引申两点,一是逆向绑定的原理和另一个用法,二,整个Databing的工作原理。

    逆向绑定:

    Databing在对页面生成一个bing对象时,会根据这个布局页面里的view生成一些代码,其中就包括了如下代码:

    // values

    // listeners

    // Inverse Binding Event Handlers

    private android.databinding.InverseBindingListener mboundView1androidTextAttrChanged =

           new android.databinding.InverseBindingListener() {

              @Override

             public void onChange() {

    // Inverse of student.name

              // is student.setName((java.lang.String) callbackArg_0)

            java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView1);          // localize variables for thread safety

            // student com.jie.databindingsimple.entity.Student student = mStudent;

    // student.name java.lang.String studentName = null;

             // student != null

            if (student)!= null)) {

             student.setName(((java.lang.String) (callbackArg_0)));

             }

          }

     };

    @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")

       public static String getTextString(TextView view) {

            return view.getText().toString();

        }

    @BindingAdapter(value = {"android:textAttrChanged"}, requireAll = false)  

     public static void setAndroidTextAttrChanged(Textview textview, InverseBindingListener inverseBindingListener) { 

              if (inverseBindingListener == null) {   

               Log.e("错误!", "InverseBindingListener为空!");      

             } else {       // 触发更新

              inverseBindingListener =inverseBindingListener ;

           }    

      }  

    先是Databing在编译时根据布局文件在生成如上代码,运行时当用户改变了文本,触发textview的text属性的改变,然后Databing根据text属性找到了getTextString方法和上边的的注解中的event,然后根据event中的android:textAttrChanged找到setAndroidTextAttrChanged方法,并将mboundView1androidTextAttrChanged属性设置进去,而textview的相关监听中判断InverseBindingListener!=null了,就调用InverseBindingListener.onChange();    onChange方法找到getTextString方法获取文本并设置给Pojo对象;以上的的代码Databing已经帮我们写好了,不过知道了这几部原理,我们也可以定义自己view逻辑。具体参考:

    https://www.colabug.com/1197309.html 使用@InverseBindingMethods实现反向绑定

    https://blog.csdn.net/zhangphil/article/details/77839555使用@InverseBindingAdapter反向绑定。

    我回头看一下Observable接口:

    package android.databinding;

    public interface Observable {

          void     addOnPropertyChangedCallback(Observable.OnPropertyChangedCallback var1);

          void     removeOnPropertyChangedCallback(Observable.OnPropertyChangedCallback var1);

         public  abstract static class OnPropertyChangedCallback { public OnPropertyChangedCallback() { }

         public   abstract void onPropertyChanged(Observable var1, int var2); }

    }

    Observable接口中有一个addOnPropertyChangedCallback方法用来添加一个接收属性改变的callback,我可以在activity中这样写,

    来监听到某个属性的改变;

    ActivityTwowayBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_twoway);

    Student student = new Student();

    binding.setStudent(student);

    student.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

    @Override

    public void onPropertyChanged(Observable observable, int i) {

    switch (i) {

    case BR.name:

        Toast.makeText(TwowayActivity.this, "name", Toast.LENGTH_SHORT).show();

        break;

        case BR.className:

        Toast.makeText(TwowayActivity.this, "classname", Toast.LENGTH_SHORT).show();

        break;

            }

         }

      });

    }

    目前双向绑定仅支持如text,checked,year,month,hour,rating,progress等绑定。其它的需要我们自定义;

    Databing原理

    https://www.jianshu.com/p/c41e7a597ac8

    自定义用法

    1、转换器

    类型转换对view赋值时有些类型需要转换,例如:color 为int值 ,

    view.setBackground 需要的是drawable参数,这个时候,需要定义类型转换,

    @BindingConversion

    public static ColorDrawable convertColorToDrawable(int color) {

      return new ColorDrawable(color);

    }

    注意这个方法必须是public static的,该方法可以定义在任何地方。

    2、适配器 @BindAdapter

    适配器开发中一个比较常见的场景就是使用ImageLoader进行加载图片,在任何地方添加适配器代码:

    @BindingAdapter("android:src",requireAll = false)

    public static void setImageUrl(ImageView view, String url) { 

          Picasso.with(view.getContext()).load(url).into(view);

    }

    这个时候可以使用下面代码进行数据绑定:当进行bind的时候,会自动调用适配器定义的函数。适配器也可以用来添加view的属性。比如我想在Textview中绑定long型的时间戳,并且按照一个格式显示。

    @android.databinding.BindingAdapter(value = {"app:date", "app:format"}, requireAll = false)

    public static void bindFormat(TextView view, String date, String format) {

      if (date == null) {

          return;

      }

    SimpleDateFormat formatter;

    if (format != null) {

          formatter = new SimpleDateFormat(format);

      }else {

          formatter = new SimpleDateFormat("yyyy-MM-dd");

      }

         Long timeStamp = Long.parseLong(date);

         view.setText(formatter.format(new Date(timeStamp)));

    }

    其中 requireAll = false表示当参数不全时,也会调用适配器函数,补全的参数会传入空值,所以要自己判断。

    关于ImageView的另一种用法:

    @BindingAdapter(value={"android:src","placeHolder"},requireAll=false)

    public static void setImageUrl(ImageViewview,Stringurl,intplaceHolder){

      RequestCreator requestCreator=Picasso.with(view.getContext()).load(url);

         if(placeHolder!=0){

              requestCreator.placeholder(placeHolder);

       } 

          requestCreator.into(view);

    }

    资源(Resources):

    可以使用正常语法的表达式访问资源:

    [html]view plaincopy

    android:padding=“@{large? @dimen/largePadding : @dimen/smallPadding}”  

    android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

    格式字符串和复数可通过提供参数判断:

    [html]view plaincopy

    android:text=“@{@string/nameFormat(firstName, lastName)}”  

    android:text=“@{@plurals/banana(bananaCount)}”  

    android:text="@{@string/nameFormat(firstName, lastName)}"

    android:text="@{@plurals/banana(bananaCount)}"

    当复数需要多个参数时,所有参数都应通过:

    [html]view plaincopy

      Have an orange  

      Have %d oranges  

    android:text=“@{@plurals/orange(orangeCount, orangeCount)}”  

      Have an orange

      Have %d oranges

    android:text="@{@plurals/orange(orangeCount, orangeCount)}"

    一些资源需要显示类型判断:

    自动安装(Automatic Setters)

    对于一个属性,Data Binding 试图找到 setAttribute 方法,与该属性的 name space 并没有什么关系,仅仅与属性本身名称有关

    例如,有关 TextView 的 android:text 属性的表达式会寻找一个 setText(String) 的方法,如果表达式返回一个 int,Data Binding会搜索的 setText(int) 方法,这里要注意:表达式要返回正确的类型,如果需要的话使用 casting,Data Binding 任然会工作,即使没有给定的名称属性存在,然后,你可以通过 Data Binding 轻松地为任何 setter “创造”属性,例如,DrawerLayout 没有任何属性,但大量的 setters,你可以使用自动 setters 来使用其中的一个:

    [html]view plaincopy

    android:layout_width=“wrap_content”     

    android:layout_height=“wrap_content”  

    app:scrimColor=“@{@color/scrim}”  

    app:drawerListener=“@{fragment.drawerListener}”/>  

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        app:scrimColor="@{@color/scrim}"

        app:drawerListener="@{fragment.drawerListener}"/>

    重命名 Setters(Renamed Setters)

    一些有 Setters 的属性按名称并不匹配,对于这些方法,属性可以通过 BindingMethods 注解相关联,还必须与一个包含BindingMethod 注解类相关联,每一个用于重命名的方法,例如  andorid:tin 属性与  setImageTintList 相关联,而不与 setTint相关

    [java]view plaincopy

    @BindingMethods({  

    @BindingMethod(type = “android.widget.ImageView”,  

    attribute =”android:tint”,  

    method =”setImageTintList”),  

    })  

    @BindingMethods({

          @BindingMethod(type = "android.widget.ImageView",

                          attribute = "android:tint",

                          method = "setImageTintList"),

    })

    上面的例子,开发者不太可能重命名编译程序,Android 框架属性已经实现了

    泛型支持

       

        ">

    ">// 左尖括号需要转义,

    include

    自定义 Setters(Custom Setters)

    有些属性需要自定义绑定逻辑,例如,对于 android:paddingLeft 属性并没有相关的setter,相反,setPadding (left、top、right、bottom)是存在,一个带有 BindingAdapter 注解的静态绑定适配器方法允许开发者自定义 setter 如何对于一个属性的调用

    Android 的属性已经创造了 BindingAdapters,举例来说,对于 paddingLeft:

    [java]view plaincopy

    @BindingAdapter(“android:paddingLeft”)  

    public static void setPaddingLeft(View view, int padding) {  

       view.setPadding(padding,  

                       view.getPaddingTop(),  

                       view.getPaddingRight(),  

                       view.getPaddingBottom());  

    }  

    @BindingAdapter("android:paddingLeft")

    public static void setPaddingLeft(View view, int padding) {

      view.setPadding(padding,

                      view.getPaddingTop(),

                      view.getPaddingRight(),

                      view.getPaddingBottom());

    }

    Binding 适配对其他定制类型非常有用,例如,自定义 loader 可以用异步载入图像

    当有冲突时,开发人员创建的 Binding 适配器将覆盖 Data Binding 默认适配器

    你也可以创建可以接收多个参数的适配器:

    [java]view plaincopy

    @BindingAdapter({“bind:imageUrl”, “bind:error”})  

    public static void loadImage(ImageView view, String url, Drawable error) {  

       Picasso.with(view.getContext()).load(url).error(error).into(view);  

    }  

    @BindingAdapter({"bind:imageUrl", "bind:error"})

    public static void loadImage(ImageView view, String url, Drawable error) {

      Picasso.with(view.getContext()).load(url).error(error).into(view);

    }

    [html]view plaincopy

    app:error=“@{@drawable/venueError}”/>  

    app:error="@{@drawable/venueError}"/>

    如果对于一个 ImageView imageUrl 和 error 都被使用,并且 imageUrl 是一个 String 类型以及 error 是一个 drawable 时,该适配器被调用

    匹配的过程中自定义 name spaces 将被忽略

    你也可以为 Android name spaces 写适配器

    绑定适配器方法可以在其处理程序中选择旧值。取旧值和新值的方法应具有所有属性的旧值,其次是新值:

    [java]view plaincopy

    @BindingAdapter(“android:paddingLeft”)  

    public static void setPaddingLeft(View view, int oldPadding, int newPadding) {  

    if (oldPadding != newPadding) {  

           view.setPadding(newPadding,  

                           view.getPaddingTop(),  

                           view.getPaddingRight(),  

                           view.getPaddingBottom());  

       }  

    }  

    @BindingAdapter("android:paddingLeft")

    public static void setPaddingLeft(View view, int oldPadding, int newPadding) {

      if (oldPadding != newPadding) {

          view.setPadding(newPadding,

                          view.getPaddingTop(),

                          view.getPaddingRight(),

                          view.getPaddingBottom());

      }

    }

    事件处理程序只能用一个抽象方法与接口或抽象类一起使用,例如:

    [java]view plaincopy

    @BindingAdapter(“android:onLayoutChange”)  

    public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,  

           View.OnLayoutChangeListener newValue) {  

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {  

    if (oldValue != null) {  

                view.removeOnLayoutChangeListener(oldValue);  

            }  

    if (newValue != null) {  

                view.addOnLayoutChangeListener(newValue);  

            }  

        }  

    }  

    @BindingAdapter("android:onLayoutChange")

    public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,

          View.OnLayoutChangeListener newValue) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

            if (oldValue != null) {

                view.removeOnLayoutChangeListener(oldValue);

            }

            if (newValue != null) {

                view.addOnLayoutChangeListener(newValue);

            }

        }

    }

    当监听器有多个方法时,它必须被分割成多个监听器,例如,View.OnAttachStateChangeListener 的方法有两种:onViewAttachedToWindow() 和 onViewDetachedFromWindow()。然后,我们必须创建两个接口来区分它们的属性和处理程序:

    [java]view plaincopy

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)  

    public interface OnViewDetachedFromWindow {  

    void onViewDetachedFromWindow(View v);  

    }  

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)  

    public interface OnViewAttachedToWindow {  

    void onViewAttachedToWindow(View v);  

    }  

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)

    public interface OnViewDetachedFromWindow {

        void onViewDetachedFromWindow(View v);

    }

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)

    public interface OnViewAttachedToWindow {

        void onViewAttachedToWindow(View v);

    }

    因为改变一个监听器也会影响另一个,我们必须有三个不同的绑定适配器,一个为每个属性和一个为两者,他们应该被设置:

    [java]view plaincopy

    @BindingAdapter(“android:onViewAttachedToWindow”)  

    public static void setListener(View view, OnViewAttachedToWindow attached) {  

    setListener(view,null, attached);  

    }  

    @BindingAdapter(“android:onViewDetachedFromWindow”)  

    public static void setListener(View view, OnViewDetachedFromWindow detached) {  

    setListener(view, detached,null);  

    }  

    @BindingAdapter({“android:onViewDetachedFromWindow”, “android:onViewAttachedToWindow”})  

    public static void setListener(View view, final OnViewDetachedFromWindow detach,  

    final OnViewAttachedToWindow attach) {  

    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {  

    final OnAttachStateChangeListener newListener;  

    if (detach == null && attach == null) {  

    newListener =null;  

    }else {  

    newListener =new OnAttachStateChangeListener() {  

    @Override  

    public void onViewAttachedToWindow(View v) {  

    if (attach != null) {  

                            attach.onViewAttachedToWindow(v);  

                        }  

                    }  

    @Override  

    public void onViewDetachedFromWindow(View v) {  

    if (detach != null) {  

                            detach.onViewDetachedFromWindow(v);  

                        }  

                    }  

                };  

            }  

    final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,  

    newListener,R.id.onAttachStateChangeListener);

    if (oldListener != null) {  

                view.removeOnAttachStateChangeListener(oldListener);  

            }  

    if (newListener != null) {  

                view.addOnAttachStateChangeListener(newListener);  

            }  

        }  

    }  

    @BindingAdapter("android:onViewAttachedToWindow")

    public static void setListener(View view, OnViewAttachedToWindow attached) {

        setListener(view, null, attached);

    }

    @BindingAdapter("android:onViewDetachedFromWindow")

    public static void setListener(View view, OnViewDetachedFromWindow detached) {

        setListener(view, detached, null);

    }

    @BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})

    public static void setListener(View view, final OnViewDetachedFromWindow detach,

            final OnViewAttachedToWindow attach) {

        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {

            final OnAttachStateChangeListener newListener;

            if (detach == null && attach == null) {

                newListener = null;

            } else {

                newListener = new OnAttachStateChangeListener() {

                    @Override

                    public void onViewAttachedToWindow(View v) {

                        if (attach != null) {

                            attach.onViewAttachedToWindow(v);

                        }

                    }

                    @Override

                    public void onViewDetachedFromWindow(View v) {

                        if (detach != null) {

                            detach.onViewDetachedFromWindow(v);

                        }

                    }

                };

            }

            final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,

    newListener, R.id.onAttachStateChangeListener);

            if (oldListener != null) {

                view.removeOnAttachStateChangeListener(oldListener);

            }

            if (newListener != null) {

                view.addOnAttachStateChangeListener(newListener);

            }

        }

    }

    上面的例子是比正常的稍微复杂,因为 View 使用添加和删除的监听者而不是为 View.OnAttachStateChangeListener 设置方法,android.databinding.adapters.listenerutil 类有助于保持跟踪,他们可能会在绑定适配器删除以前的 listener

    https://blog.csdn.net/willba/article/details/71552525时间监听

    事件绑定方式

    监听方法调用控件常用的监听都可以用事件绑定来搞定,比如以下这几种

    android:onClick

    android:onLongClick

    android:onTextChanged

    其他事件同理

    Lambda表达式

    在xml中我们还可以应用lambda表达式来书写

    android:onClick="@{(view) -> activity.onTextClick(view)}"

    统计一下事件监听表达的用法

    由此可见事件监听绑定有很多种用法,我们在项目中最好统一规范应用其中一种,避免个性化

    android:onClick="onTextClick"

    android:onClick="@{(view) -> activity.onTextClick(view)}"

    android:onClick="@{activity::onTextClick}"

    android:onClick="@{activity.onTextClick}"

    问题汇总:

    直接 Binding

    当一个 Variable 或 Observable 变化时,binding 会在下一帧前被计划要求改变,但是有很多次在 Binding 时必需立即执行,要强制执行,使用 executePendingBindings() 方法

    后台线程

    只要它不是一个集合,你可以在后台中改变数据模型,在判断是否要避免任何并发问题时,Data Binding 会对每个 Variable/field本地化

    线程问题

    无论在子线程还是主线程中更新ViewModle数据,Binding框架在更新Ui时都是将交给主线更关心uI,通常来说不会需要我们再做同步的事情,但是如果是RecycleView,则需要再UI线程中更新数据,因为列表在滑动过程中不断binging新Item,UI线程会不断读取viewModle中的数据(get方法),如果此时再子线程更新了uI线程正在读取的数据容易出现线程问题。

    相关文章

      网友评论

          本文标题:DataBinding 开发知识汇总

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