Data Binding

Data Binding Library

数据绑定库编写声明式布局,尽量减少绑定应用程序逻辑和布局所需的代码,减少布局绑定相关代码

  • build environment

    1
    2
    3
    4
    5
    6
    android{
    ...
    dataBinding {
    enabled = true
    }
    }
  • Data Binding Compiler V2

    1
    2
    android.databinding.enableV2=true
    向后不兼容
  • Data Binding 布局文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    <import type="java.util.List" />

    <import type="android.view.View" />

    <import type="com.willkernel.www.databindingdemo.User" />

    <import type="com.willkernel.www.databindingdemo.DoSomething" />

    <!--<variable-->
    <!--name="view"-->
    <!--type="View" />-->

    <variable
    name="user"
    type="User" />

    <variable
    name="userClick"
    type="com.willkernel.www.databindingdemo.MainActivity.UserClick" />

    <variable
    name="ds"
    type="DoSomething" />

    </data>

    <android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{userClick::onUserClick}"
    android:text="@{user.firstName}" />

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{() -> userClick.onUserClick(user)}"
    android:text="@{user.firstName}" />
    <!--android:onClick="@{userClick::onUserClick}"-->
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{(v) -> userClick.onUserClick(v,user)}"
    android:text="@{user.firstName}" />

    <CheckBox
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onCheckedChanged="@{(cb,isChecked) -> userClick.onCheckedChanged(user,isChecked)}"
    android:text="@{user.lastName}" />

    <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onLongClick="@{(v)->userClick.onLongClick(v,user)}"
    android:text="@{user.lastName}" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.firstName}"
    android:visibility="@{user.isAdult ? View.VISIBLE:View.INVISIBLE}" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{DoSomething.capital(user.firstName)}" />
    </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
    </layout>
    • data标签下的variable描述将会在布局中使用的属性

      1
      <variable name="user" type="com.example.User"/>
    • 使用”@{}” 语法获取属性

      1
      2
      3
      <TextView android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{user.firstName}"/>
    • 三种情况databinding都可以获取属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      1. public final String firstName;

      2. private final String firstName;
      public String getFirstName() {
      return this.firstName;
      }
      3. private final String firstName;
      public String firstName() {
      return this.firstName;
      }
  • 数据绑定,默认基于布局文件名称生成绑定类,将其转换为Pascal实例并添加Binding后缀,例如布局文件activity_main.xml,生成的类为MainActivityBinding,持有布局所有属性的绑定方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    User user = new User();
    user.firstName = "firstName";
    user.lastName = "lastName";
    user.isAdult = true;
    binding.setUser(user);
    binding.setUserClick(new UserClick());
    }


    也可以这样获取View
    ActivityMainBinding binding =ActivityMainBinding.inflate(getLayoutInflater());
    MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
  • ListView,RecyclerView适配器中绑定

    1
    2
    3
    ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
    //or
    ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
  • 如果通过其他方式填充的布局,需要调用bind方法

    1
    MyLayoutBinding binding =MyLayoutBinding.bind(viewRoot);
  • 另外的方式

    1
    2
    3
    ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
    ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
  • 布局中的views将没有设置id的会生成private final ID,设置id的会生成public final ID ,比findViewById速度更快

  • 生成的方法

    • variable

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <data>
      <import type="android.graphics.drawable.Drawable"/>
      <variable name="user" type="com.example.User"/>
      <variable name="image" type="Drawable"/>
      <variable name="note" type="String"/>
      </data>

      public abstract com.example.User getUser();
      public abstract void setUser(com.example.User user);
      public abstract Drawable getImage();
      public abstract void setImage(Drawable image);
      public abstract String getNote();
      public abstract void setNote(String note);
    • ViewStubs
      因为在显示后会从view层级中消失,所以view的binding对象也会被回收

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @NonNull
      public final android.databinding.ViewStubProxy viewStub;

      binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
      @Override
      public void onInflate(ViewStub stub, View inflated) {
      Log.e(TAG, "inflated=" + inflated + " stub=" + stub);
      }
      });

      if (!binding.viewStub.isInflated()) {
      binding.viewStub.getViewStub().inflate();
      }
  • 事件处理两种方式

    • 方法引用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
       public class MyHandlers {
      public void onClickFriend(View view) { ... }
      }

      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
      <variable name="handlers" type="com.example.MyHandlers"/>
      <variable name="user" type="com.example.User"/>
      </data>
      <LinearLayout
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
      <TextView android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{user.firstName}"
      android:onClick="@{handlers::onClickFriend}"/>
      </LinearLayout>
      </layout>
    • 监听绑定

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
       public class Presenter {
      public void onSaveClick(Task task){}
      }
      绑定点击事件到类
      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
      <variable name="task" type="com.android.example.Task" />
      <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
      <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
      </layout>
    • “@{() -> presenter.onSaveClick(task)}”可以忽略所有参数,也可以写上参数

      1
      android:onClick="@{(view) -> presenter.onSaveClick(task)}"
    • 如果类中有参数View

      1
      2
      3
      4
      5
       public class Presenter {
      public void onSaveClick(View view, Task task){}
      }
      绑定点击事件
      android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
    • 可以使用lambda表达式添加更多参数

      1
      2
      3
      4
      5
      6
       public class Presenter {
      public void onCompletedChanged(Task task, boolean completed){}
      }

      <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
    • 如果类中的方法返回值不是void,点击事件的绑定表达式返回值应该和方法一样,如果返回值因为空对象不能判断,会返回对象的默认值

      1
      2
      3
      4
       public class Presenter {
      public boolean onLongClick(View view, Task task){}
      }
      android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
    • 可以使用void作为三元运算符的标记

      1
      android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
    • 避免复杂监听,有些默认的点击事件处理的实现

      1
      2
      3
      4
      5
      SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick

      ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomOut

      ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut
Layout Details
  • data元素标签下,可以导入多个类

    1
    2
    3
    <data>
    <import type="android.view.View">
    </data>
  • binding表达式中可以使用View

    1
    2
    3
    4
    5
    <TextView
    android:text="@{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
  • 导入类名冲突可以使用别名

    1
    2
    3
    <import type="android.view.View"/>
    <import type="com.example.real.estate.View"
    alias="Vista"/>
  • 导入类可以设置变量

    1
    2
    3
    4
    5
    6
    <data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
    </data>
  • 引用静态变量和方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
    </data>

    <TextView
    android:text="@{MyStringUtils.capitalize(user.lastName)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
  • 如果variable实现了android.databinding.Observable或者observable collection,应该在反映在type中,当没有实现上述接口时,变量将不会被观察Observer

  • 当横竖屏不同布局时,变量会整合,不同布局不要有命名冲突
  • binding 类的主要方法,其中context是来自rootview的context
    binding.getRoot().getContext()
  • 重命名Binding对象

    1
    2
    <data class="MainAty">
    MainAty binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  • 设置Binding包名

    1
    2
    3
    4
    当前应用包名,这种方式会导致Binding对view id绑定失效
    <data class=".MainAty">
    重新设置完整的包名
    <data class="com.main.MainAty">
  • include 绑定变量

    1
    2
    <include layout="@layout/contact"
    bind:user="@{user}"/>
  • include布局元素不能作为merge的子元素

  • 表达式语法

    1
    2
    3
    4
    exapmples:
    android:text="@{String.valueOf(index + 1)}"
    android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
    android:transitionName='@{"image_" + id}'
  • 非空判断??

    1
    2
    3
    android:text="@{user.displayName ?? user.lastName}"
    等价于
    android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  • 规避空指针异常,当variable为空时,属性值会显示相应的默认值

  • 集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    \<import type="java.util.List" />
    <import type="android.util.SparseArray" />
    <import type="java.util.Map" />
    <variable
    name="list"
    type="List&lt;String&gt;" />
    <variable
    name="sparse"
    type="SparseArray&lt;String&gt;" />
    <variable
    name="map"
    type="Map&lt;String,String&gt;" />
    <variable
    name="index"
    type="int" />
    <variable
    name="key"
    type="String" />

    android:text="@{list[index]}"

    android:text="@{sparse[index]}"

    android:text="@{map[key]}"
  • 字符串引用

    1
    2
    3
    android:text='@{map["firstName"]}'
    android:text="@{map[`firstName`}"
    android:text="@{map['firstName']}"
  • 表达式中的资源引用

数据对象
  • 数据自动更新通知机制

    1
    2
    3
    Observable objects
    Observable fields
    Observable collections
  • Observable Objects 继承自BaseObservable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
     public class BaseObservable implements Observable {
    private transient PropertyChangeRegistry mCallbacks;

    public BaseObservable() {
    }

    @Override
    public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
    synchronized (this) {
    if (mCallbacks == null) {
    mCallbacks = new PropertyChangeRegistry();
    }
    }
    mCallbacks.add(callback);
    }

    @Override
    public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
    synchronized (this) {
    if (mCallbacks == null) {
    return;
    }
    }
    mCallbacks.remove(callback);
    }

    /**
    * Notifies listeners that all properties of this instance have changed.
    */
    public void notifyChange() {
    synchronized (this) {
    if (mCallbacks == null) {
    return;
    }
    }
    mCallbacks.notifyCallbacks(this, 0, null);
    }

    /**
    * Notifies listeners that a specific property has changed. The getter for the property
    * that changes should be marked with {@link Bindable} to generate a field in
    * <code>BR</code> to be used as <code>fieldId</code>.
    *
    * @param fieldId The generated BR id for the Bindable field.
    */
    public void notifyPropertyChanged(int fieldId) {
    synchronized (this) {
    if (mCallbacks == null) {
    return;
    }
    }
    mCallbacks.notifyCallbacks(this, fieldId, null);
    }
    }
  • 在getter()添加@Bindable注解,在setter()中添加notifyPropertyChanged(BR.lastName);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     private static class User extends BaseObservable {
    private String firstName;
    private String lastName;
    @Bindable
    public String getFirstName() {
    return this.firstName;
    }
    @Bindable
    public String getLastName() {
    return this.lastName;
    }
    public void setFirstName(String firstName) {
    this.firstName = firstName;
    notifyPropertyChanged(BR.firstName);
    }
    public void setLastName(String lastName) {
    this.lastName = lastName;
    notifyPropertyChanged(BR.lastName);
    }
    }

    上面user例子,实现自动刷新UI

    @Bindable
    public boolean isAdult() {
    return isAdult;
    }

    public void setAdult(boolean adult) {
    isAdult = adult;
    notifyPropertyChanged(BR.adult);
    }
    user.isAdult = !user.isAdult;
    binding.setUser(user);
  • Observable Fields 不需要写setter() getter(),需要初始化new Observable***()

    1
    2
    public class ObservableParcelable<T extends Parcelable> 
    extends ObservableField<T> implements Parcelable, Serializable
  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     private static class User {
    public final ObservableField<String> firstName =
    new ObservableField<>();
    public final ObservableField<String> lastName =
    new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
    }

    user.firstName.set("Google");
    int age = user.age.get();
  • Observable Collections

    • ObservableArrayMap

      1
      2
      3
      4
      5
      6
      7
      ObservableArrayMap<String, Object> observableMap = new ObservableArrayMap<>();
      observableMap.put("firstName", "Google");
      observableMap.put("lastName", "Inc.");
      observableMap.put("age", 17);
      binding.setObservableMap(observableMap);

      android:text='@{String.valueOf(observableMap["age"])}'
    • ObservableArrayList

      1
      2
      3
      4
      5
      6
      7
      ObservableArrayList<Object> list = new ObservableArrayList<>();
      list.add("Google");
      list.add("Inc.");
      list.add(17);
      binding.setObservableList(list);

      android:text="@{String.valueOf(observableList[index])}"
Advanced Binding 高级绑定
  • 动态变量
    RecyclerView.Adapter中在onBindViewHolder(VH, int)设置值,其中BindingHolder有一个getBinding()方法

    1
    2
    3
    4
    5
    public void onBindViewHolder(BindingHolder holder, int position) {
    final T item = mItems.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
    }
  • 立即绑定
    当变量或观察者发生变化时,绑定会在下一帧绘制时发生改变,然而有时候需要强制重新绑定

    1
    android.databinding.ViewDataBinding.executePendingBindings()
  • 后台线程 可以在后台线程改变数据model,只要不是集合,数据绑定本地化每个变量,规避线程并发问题

属性设置
  • 自动设置属性
    布局文件

    1
    2
    3
    4
    5
    6
    <ImageView
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:scaleType="centerCrop"
    app:imageUrl="@{user.imageUrl}"
    app:placeHolder="@{@drawable/place}" />
  • 可以在任意类中绑定属性值设置方法,通常以模块,view自定义命名的Bindings类

    1
    2
    3
    4
    5
    6
    7
    8
    @BindingAdapter(value = {"imageUrl", "placeHolder"}, requireAll = false)
    public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
    if (!TextUtils.isEmpty(url)){
    Glide.with(imageView.getContext()).load(url).into(imageView).onLoadStarted(placeHolder);
    } else {
    imageView.setImageDrawable(placeHolder);
    }
    }
  • 重命名setters,Android 实现了很多BindingAdapters,查看TextViewBinding

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @BindingMethods({
    @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
    @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
    @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
    @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
    @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
    @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
    @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
    @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
    @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
    @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
    })
  • 自定义设置方法

    • 有下面的方法setPadding(int left, int top, int right, int bottom)
      没有setPaddingLeft(),但是有android:paddingLeft属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @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());
      }
      }
    • 一个抽象方法的接口或抽象类的事件处理,注意,此方法必须在自定义View中编写,下面的是官方文档代码示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
       @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);
      }
      }
      }
    • 如果是多个抽象方法的事件处理

      1
      2
      3
      4
      5
      6
      7
      8
      9
       @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
      public interface OnViewDetachedFromWindow {
      void onViewDetachedFromWindow(View v);
      }

      @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
      public interface OnViewAttachedToWindow {
      void onViewAttachedToWindow(View v);
      }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     @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实现自定义监听事件处理
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
       public class ColorPicker extends View {
      private int color;

      public ColorPicker(Context context) {
      super(context);
      }

      public ColorPicker(Context context, @Nullable AttributeSet attrs) {
      super(context, attrs);
      }

      public ColorPicker(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      }

      public int getColor() {
      return color;
      }

      public void setColor(int color) {
      this.color = color;
      invalidate();
      }

      @Override
      protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);
      canvas.drawColor(getResources().getColor(color));
      }

      public void removeListener(OnColorChangeListener oldListener) {
      oldListener.onColorChange(this, color);
      Log.e("color", "remove");
      }

      public void addListener(OnColorChangeListener newListener) {
      newListener.onColorChange(this, color);
      Log.e("color", "add");
      }

      public interface OnColorChangeListener {
      void onColorChange(ColorPicker view, int newColor);
      }

      @BindingAdapter("onColorChange")
      public static void setColorChangeListener(ColorPicker view,
      ColorPicker.OnColorChangeListener oldListener,
      ColorPicker.OnColorChangeListener newListener) {
      Log.e("oldListener", "="+oldListener);
      Log.e("newListener", "="+newListener);
      if (oldListener != null) {
      view.removeListener(oldListener);
      }
      if (newListener != null) {
      view.addListener(newListener);
      }
      }
      }

      布局引用
      <com.willkernel.www.databindingdemo.ColorPicker
      android:id="@+id/colorPicker"
      android:layout_width="100dp"
      android:layout_height="100dp"
      app:color="@{color}"
      app:onColorChange="@{(v,color)->main.colorChanged(v,color)}" />

      MainActivity handler中
      public void colorChanged(int color) {
      Log.e(TAG, "colorChanged=" + color);
      }

      binding.setColor(R.color.colorAccent);
    转换
  • 对象类型转换,需要使用public static修饰方法

    1
    2
    3
    4
    5
    @BindingConversion
    public static String convertDateToText(Date date){
    DateFormat dateFormat=DateFormat.getDateInstance();
    return dateFormat.format(date);
    }
  • 自定义转换 颜色值int color转换为ColorDrawable,转换发生在setter level,不允许两种类型混合使用,int和drawable在同一个三元运算符中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <View
    android:background="@{isError ? @color/red : @color/white}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

    @BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
    }
willkernel wechat
关注微信公众号
帅哥美女们,请赐予我力量吧!