Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android官方數據綁定框架DataBinding

Android官方數據綁定框架DataBinding

編輯:關於Android編程

一、Data Binding是什麼?

2015年的Google IO大會上,Android 團隊發布了一個數據綁定框架(Data Binding Library),官方原生支持 MVVM 模型。以後可以直接在 layout 布局 xml 文件中綁定數據了,無需再 findViewById 然後手工設置數據了。其語法和使用方式和 JSP 中的 EL 表達式非常類似。

Data Binding Library 是一個 support 庫,支持 Android 2.1+ 版本 (API level 7+)。 由於該框架需要使用編譯器來生成很多代碼,所以需要配合新版本的 Android Studio (1.3.0-beta1 + 版本)才能使用,Gradle 插件1.5.0-alpha1以上。

例:



   
       
   
   
       
       
   

二、配置環境

Android Studio版本是1.3+,更新Support repository到最新的版本。
新建一個project,在dependencies中添加以下依賴

classpath "com.android.databinding:dataBinder:1.0-rc1"

新建module,並且在module的build.gradle文件中添加

apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

Studio1.5+忽略以上兩點,只需在對應module的build.gradle中添加

android {
    ....
    dataBinding {
        enabled = true
    }
}

ps:即使依賴的庫中使用了data binding,該module也必須在build.gradle中配置

三、體驗第一個data binding

布局文件



   
       
   
   
       
       
   

數據對象

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() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

綁定數據

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

Alternatively, you can get the view via:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

在 ListView 或者 RecyclerView 的Adapter中使用:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

綁定事件

例如,如果你的數據對象中有兩個方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
    public void onClickEnemy(View view) { ... }
}


   
       
       
   
   
       
       
   

四、詳細

導包


    

像java一樣,java.lang.* 自動導入,可以直接使用

當存在同名的類時,其中一個可以使用別名


導入類還可以用於在表達式中引用靜態屬性和方法


    
    

變量

自動生成的binding類會為每個聲明的變量生成setter和getter方法,在調用setter方法之前變量的值為java默認值。
當表達式中需要使用時,會自動生成一個名為“context”的變量,“context”的值是由根布局的getContext()獲得,如果手動聲明了一個名為“context”的變量,默認的會被覆蓋。

自定義Binding類名

默認Binding類會根據布局文件名自動生成,放在module包下的databinding包中,如布局文件contact_item.xml會自動生成ContactItemBinding,如果module包名是com.example.my.app,這個類將被放在com.example.my.app.databinding下。通過class屬性,可以修改Binding類的類名和所在包。

databinding包下,類名為ContactItem


    ...

module包下


    ...

指定包名


    ...

Includes

使用include時,需要將變量傳遞到被包含的布局中,此時name.xmlcontact.xml中必須有user變量。



   
       
   
   
       
       
   

Data binding不支持下直接的子元素使用include,比如下面的就不支持:



   
       
   
   
       
       
   

五、表達式

普通

以下表達式和java的一樣
* 算術表達式 + - / * %
* 字符串連接 +
* 邏輯運算符 && ||
* 位運算符 & | ^
* 一元運算符 + - ! ~
* 位移運算符 >> >>> <<
* 關系運算符 == > < >= <=
* instanceof
* 分組 ()
* 字面值 - 字符, 字符串, 數字, null
* 類型轉換
* 方法調用
* 訪問屬性
* 訪問數組 []
* 三元運算符 ?:

例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

缺少的

java中可以使用而這裡不能使用的
* this
* super
* new
* Explicit generic invocation

判斷非空運算符

當表達式左邊不為null時使用左邊的值,如果為null使用右邊的
android:text="@{user.displayName ?? user.lastName}"
等效於
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

避免了 NullPointerException

生成的data binding代碼會自動檢查空值,避免空指針。例如表達式@{user.name},如果usernulluser.name將使用默認值nulluser.age將使用默認值0。

集合(Collections)


    
    
    
    
    
    
    
    

…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map["firstName"]}"

資源文件(Resources)

直接在表達式中使用resources

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

Format strings and plurals may be evaluated by providing parameters:

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

When a plural takes multiple parameters, all parameters should be passed:

  Have an orange
  Have %d oranges

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

Some resources require explicit type evaluation.

Type Normal Reference Expression Reference String[] @array @stringArray int[] @array @intArray TypedArray @array @typedArray Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList

六、數據對象

任何一個簡單的Java對象(POJO)都可以用來綁定,但是修改一個POJO不能夠觸發UI更新。
以下是3種數據改變通知機制,Observable objects, observable fields, 和 observable collections,當這3種數據對象綁定到UI,並且數據改變時UI會自動更新

Observable Objects

實現 android.databinding.Observable 接口。為了方便,Android提供了基類BaseObservable,實現了監聽器的注冊機制。

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);
   }
}

Bindable 注解編譯時在 BR 中生成一個entry。 BR是在編譯時生成在module的一個類,功能與R.java類似。

ObservableFields

屬性較少時可以使用ObservableField,包含ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable,簡單的POJO就可以實現。

private static class User {
   public final ObservableField firstName =
       new ObservableField<>();
   public final ObservableField lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

訪問值時使用:

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

Observable Collections

ObservableArrayMap(當key是引用類型時)

ObservableArrayMap user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

    
    

ObservableArrayList(當key是整數時)

ObservableArrayList

    
    
    

七、生成 Binding

生成的binding類引用了layout中的View,就如前面說的,Binding的類名和包名都可以定制,生成的binding類都繼承自ViewDataBinding

創建

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

If the layout was inflated using a different mechanism, it may be bound separately:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

帶ID的View

如果布局中某個View設置了id,則生成的binding類中會包含:

public final TextView firstName;
public final TextView lastName;

ViewStubs


    
        
    
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        ViewStubBinding binding = DataBindingUtil.bind(inflated);
        User user = new User("fee", "lang");
        binding.setUser(user);
    }
});

高級用法

動態變量

以 RecyclerView 為例,Adapter 的 DataBinding 需要動態生成,因此我們可以在 onCreateViewHolder 的時候創建這個 DataBinding,然後在 onBindViewHolder 中獲取這個 DataBinding。

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

當一個變量改變時,binding會在下一幀時改變UI,需要立刻執行,可以使用executePendingBindings()方法。

八、Attribute Setters

當一個綁定的值改變時,可以指定調用哪個方法來設置值

Automatic Setters

沒有配置自定義屬性,data binding會自動找到對應的setAttribute方法(命名空間無所謂),注意表達式的返回值,必要的時候進行類型轉換。

Renamed Setters

如果不想調用根據名字匹配的setter,可以通過BindingMethods注解重新匹配。

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

通常開發者不需要重命名setters

Custom Setters

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

還可以接收多個參數

@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);
}

這個適配器會在imageUrl和error都在ImageView中設置,imageUrl是字符串,error時drawable時調用

Binding adapter methods may optionally take the old values in their handlers. A method taking old and new values should have all old values for the attributes come first, followed by the new values:

@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: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);
        }
    }
}

當一個listener有多個方法時,必須分成多個listener。比如View.OnAttachStateChangeListener有兩個方法:onViewAttachedToWindow()onViewDetachedFromWindow(),就必須創建兩個接口來區分他們。

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

因為改變一個listener經常會影響另一個,所以我們需要有3個不同的綁定適配器。

@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);
        }
    }
}

The above example is slightly more complicated than normal because View uses add and remove for the listener instead of a set method for View.OnAttachStateChangeListener. The android.databinding.adapters.ListenerUtil class helps keep track of the previous listeners so that they may be removed in the Binding Adaper.

By annotating the interfaces OnViewDetachedFromWindow and OnViewAttachedToWindow with @TargetApi(VERSION_CODES.HONEYCOMB_MR1), the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).

九、轉換器(Converters)

Object Conversions

當binding表達式返回一個對象時,會從automatic, renamed, 和 custom setters 中選擇一個,這個對象會被轉換成選擇的setter的參數類型。
這方便了用ObservableMaps保存數據,例如:

userMap 返回了一個 Object ,這個 Object 將會自動轉換成 setText(CharSequence)的參數類型。當參數類型不明確時,開發者需要在表達式中進行轉換。

Custom Conversions

有時特殊的類型需要自動轉換。比如,當設置背景時:

這裡,背景是一個 Drawable,但是顏色是一個整數,需要把 int 轉換成 ColorDrawable,可以使用帶有 BindingConversion 注解的一個靜態方法:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

Note:不要將上面的和以下混淆

   
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved