Android MVVM 之DataBinding,BindingAdapter及component

简介

DataBinding是MVVMAndroid上的一种实现,支持双向绑定,自动刷新。是ButterKnife等APT框架的有效替代方案。

基本用法

DataBindingUtil

生成实例,会有一定的规则,layout通过文件名生成,View通过id生成,通过dataBinding.setVariable(Variable variable);来实现数据的绑定。

自定义类名

通过自定义类名,这样就可以避开上面的规则

<data class="CustomBinding"></data> //在app_package/databinding下生成CustomBinding;
<data class=".CustomBinding"></data> //在app_package下生成CustomBinding;
<data class="com.example.CustomBinding"></data> // 明确指定包名和类名。

变量/方法/事件绑定/lambda

"user" type="com.example.User"/>

android:text="@{user.firstName}" // 变量绑定
android:onClick="@{presenter.onClick}"// 事件绑定,方法引用
android:text='@{"" + user.age}'//age是int值,在java中手抖会索引到int值的resId
android:onClick="@{() -> presenter.onClickListenerBinding(employee)}"//lambda,签名可不一致

导入/alias

<data>
    <import type="android.view.View"/>
    <import type="com.example.User"/>
    <import type="com.mvvm.model.User" alias="MyUser"/>//类名相同,用alias区分
    <variable name="user" type="User">
    <variable name="user" type="MyUser">
</data>
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"//依赖了View,需要导入,如java,java.lang.*包不需要导入

泛型支持

type="com.example.User"/>
type="java.util.List"/>
"user" type="User"/>
"userList" type="List<User>"/>// 左尖括号需要转义,

include

 <data>
       <variable name="user" type="com.example.User"/>
   data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   LinearLayout>

避免空指针/ ?? /Resource

android:text="@{user.userName ?? user.realName}"//如果userName为null,则显示realName

android:text="@{@string/nameFormat(firstName, lastName)}" <string name="nameFormat">%s, %sstring>//组合字符串
android:text="@{user.firstName}"//如果user为null,则user.firstName = null
android:marginLeft="@{@dimen/margin + @dimen/avatar_size}"// 资源直接相加

重复表达式/隐式更新

<CheckBox android:id=”@+id/seeAds“/>
<ImageView android:visibility=“@{seeAds.checked ?
  View.VISIBLE : View.GONE}”/>//checkbox改变时,ImageView随之改变

EL表达式

算术表达式 : + - / * %

字符串拼接 : +

逻辑表达式 : && ||

二元运算符 : & | ^

一元运算符 : + - ! ~

移位操作符 : >> >>> <<

比较操作符 : == > < >= <=

Instanceof

分组操作符 : Grouping ()

字面量 : character, String, numeric, null

强转Cast,方法调用

Field 访问

Array 访问 []

三元运算符 : ?:

聚合判断 : “??”

不支持this, super, new, 以及显示的泛型调用

Observable:ViewModel改变与UI自动更新

  • BaseObservable: 继承类,字段的set方法需要添加注解@Bindable,并且调用notifyPropertyChanged(BR.field);方法,通过其继承关系知其继承自Observable.
    ps,如果不想使用BaseObservable,可以实现Observable 来实现自己的更新,这时候需要借助PropertyChangedRegistry即可。
public class User extends BaseObservable{
private boolean isFollow;
 @Bindable
    public boolean isFollow() {
        return isFollow;
    }
    public void setIsFollow(boolean isFollow) {
            this.isFollow = isFollow;
            notifyPropertyChanged(BR.follow);
            //notifyChange:刷新所有的值域
            //notifyPropertyChanged: 只更新对应BR的flag
        }
}
  • ObserableField : 泛型类,除此之外,databinding提供有基本类型ObservableInt,ObservableBoolean
public static class User {
    public final ObservableField realName = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
};

//java中的get/set方法
int age = user.age.get();
user.realName.set("Google");
  • Collections: 包含ObservableArrayList/ObservableArrayMap
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("age", 17);

ObservableArrayList<Object> userlist = new ObservableArrayList<>(); user.add(17);
android:text='@{user["age"]}'//从user中找到age android:text='@{userlist[0]}'//从userlist中根据 `index` 找到 `age`

动态变量:

在不知道具体生成的binding类的时候(Recyclerview的多种ViewHolder)

public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {

    protected final T mBinding;

    public BindingViewHolder(T binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    public T getBinding() {
        return mBinding;
    }
}

// 通过上面的泛型获取相应的ViewDataBinding
public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

高级用法

自定义属性

  • Setter,通过继承 第三方View,加上自定义的Set方法,来支持 databinding
// SimpleDraweeView extends ImageView
public void setUrl(String url) {
    view.setImageURI(TextUtils.isEmpty(url) ? null : Uri.parse(url));
}

 app:url=“@{@string/url}”
  • BindingAdapter,没有对应的 Set或者方法签名不同的时候使用,和 属性动画的Wrapper方法类似
@BindingAdapter(value ={"bind:imageUrl", "bind:error"},requireAll = false)
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
  • BindingMethods,本身支持Set,但是xml中的属性名字和set方法名字不对应的时候使用。
@BindingMethods({
       @BindingMethod(type = “android.widget.ImageView”,
                      attribute = “android:tint”,
                      method = “setImageTintList”),
})
  • BindingConversion,当想设置的和需要的不是一个类型时,如:View背景(我们设置int类型的color,而需要的是 一个带颜色的 drawable)
@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);
}

双向绑定: @=

需要注意的是双向绑定的死循环问题,需要在改变之前判断是否相同,ps



public void change(Editable s) {
    final String text = s.toString();
    if (!text.equals(name.get()) {//不相同才改变
        name.set(text);
    }
}

改变监听

addOnPropertyChangedCallback: Model属性改变时回调发生
OnRebindCallback: view发生改变重复绑定时触发

mModel.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable observable, int i) {
        if (i == BR.name) {
            Toast.makeText(TwoWayActivity.this, "name changed",
                    Toast.LENGTH_SHORT).show();
        } else if (i == BR.password) {
            Toast.makeText(TwoWayActivity.this, "password changed",
                    Toast.LENGTH_SHORT).show();
        }
    }
});

Component

通过DataBindingUtil.setDefaultComponent来设置不同环境下不同的Component
设置之后就可以使用该Component提供的Adapter方法,默认不设置是全局使用,可以理解为作用域。

public interface TestableAdapter {
    @BindingAdapter("android:src")
    void setImageUrl(ImageView imageView, String url);
}

public interface DataBindingComponent {
    TestableAdapter getTestableAdapter();
}

DataBindingUtil.setDefaultComponent(myComponent); 
 ‐ or ‐
binding = MyLayoutBinding.inflate(layoutInflater, myComponent);

性能

  • 0反射
  • 避免重复计算(如生成公用的本地变量)
  • 只遍历索引一次,而不是每次都 findById

注意事项

  • 在列表如Recyclerview中使用DataBinding
 public void bindTo(User user) {
    mBinding.setUser(user);
    mBinding.executePendingBindings();//数据绑定刷新所有挂起的更改
  }
  • RecyclerviewOnRebindCallback,会监听到数据无效标识。进而改变相应的item
 holder.getBinding().addOnRebindCallback(new OnRebindCallback() {
  //...
    public void onCanceled(ViewDataBinding binding) {
      if (mRecyclerView == null || mRecyclerView.isComputingLayout()) {
        return;
      }
      int position = holder.getAdapterPosition();
      if (position != RecyclerView.NO_POSITION) {
        notifyItemChanged(position, DATA_INVALIDATION);
      }
    }

  });

使用DataBinding,支持多种TypeRecyclerview-Adapter实践:
DataBindingDemo

相关学习文章:

  • 棉花糖给 Android 带来的 Data Bindings(数据绑定库)

  • 从零开始的Android新项目7 - Data Binding入门篇

  • 从零开始的Android新项目8 - Data Binding高级篇

你可能感兴趣的:(项目架构,Android基础,android,mvvm,databindin,框架)