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