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

<variable name="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.*包不需要導入

泛型支持

<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List&lt;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
</br>

android:text="@{@string/nameFormat(firstName, lastName)}"
<string name="nameFormat">%s, %s</string>//組合字符串
</br>

android:text="@{user.firstName}"//如果user爲null,則user.firstName = null
<br/>

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<String> 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);
<br/>
ObservableArrayList<Object> userlist = new ObservableArrayList<>();
user.add(17);
<br/>
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)
<TextView
    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);
}

雙向綁定: @=

需要注意的是雙向綁定的死循環問題,需要在改變之前判斷是否相同,ps

<EditText android:text=“@{user.name}”
    android:afterTextChanged=“@{callback.change}”/>

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

相關學習文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章