分類
Android Uncategorized

Two-way data binding (雙向數據綁定)

前言

使用單向數據綁定可以在屬性上設置數值,並可設定對該屬性的變化作出反應的監聽器。如下

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>

雙向數據綁定提供了此過程的捷徑。如下

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@={viewmodel.rememberMe}"
/>

注意在 @={} 表示法中的 = 符號表示了同時接受資料變化以及監聽更新。
為了對數據的變化做出反應,可以讓佈局變量成為 Observable 的實現,通常是 BaseObservable,並使用 @Bindable 註釋,如下

public class LoginViewModel extends BaseObservable {
    // private Model data = ...
    @Bindable
    public Boolean getRememberMe() {
        return data.rememberMe;
    }
    public void setRememberMe(Boolean value) {
        // Avoids infinite loops.
        if (data.rememberMe != value) {
            data.rememberMe = value;
            // React to the change.
            saveData();
            // Notify observers of a new value.
            notifyPropertyChanged(BR.remember_me);
        }
    }
}

由於 bindable 屬性的 getter 方法稱為 getRememberMe(),因此屬性的相應 setter 方法會自動使用名稱 setRememberMe()。
關於使用 BaseObservable 和 @Bindable 的更多訊息,參考 Work with observable data objects.

Two-way data binding using custom attributes

該平台為最常見的雙向屬性和監聽器提供雙向數據綁定的實現,使用者可以將當作應用程序的一部分。
若使用者想在自定義屬性上使用雙向資料綁定則必須了解如何使用 @InverseBindingAdapter 和 @InverseBindingMethod 註解。
例如,如果要在名稱為 MyView 的自定義視圖中對 “time” 屬性啟用雙向數據綁定,必須完成以下步驟
1.註釋設置初始值的方法,並在使用 @BindingAdapter 更改時進行更新

@BindingAdapter("time")
public static void setTime(MyView view, Time newValue) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue;
    }
}

2.使用 @InverseBindingAdapter 註釋從視圖中讀取數值的方法

@InverseBindingAdapter("time")
public static Time getTime(MyView view) {
    return view.getTime();
}

此時,data binding library 知道數據變化時要執行的操作(呼叫使用 @BindingAdapter 註釋的方法)以及視圖屬性變化時調用的內容(它呼叫 InverseBindingListener)。
為此,需要在視圖上設置一個監聽器。它可以是與自定義視圖關聯的自定義監聽器,也可以是通用事件,例如失去焦點或文本更改。
將 @BindingAdapter 註釋添加到為該屬性的變化設置監聽器的方法,如下

@BindingAdapter("app:timeAttrChanged")
public static void setListeners(
        MyView view, final InverseBindingListener attrChange) {
    // Set a listener for click, focus, touch, etc.
}

這個 Listener 包含了 InverseBindingListener 作為參數,可以使用 InverseBindingListener 告知 data binding library 該屬性已更改。接著,系統可以開始使用 @InverseBindingAdapter 呼叫註釋方法,依此類推。
注意每個雙向綁定都會生成一個合成事件屬性( synthetic event attribute)。此屬性與基本屬性具有相同的名稱,但具有後綴 “AttrChanged”。
合成事件屬性允許 data binding library 建立使用 @BindingAdapter 註釋的方法,以將事件監聽器連結到 View 的相應實體。
在實作中,該監聽器包括一些特殊的邏輯,包括用於單向數據綁定的監聽器。相關範例,參考 TextViewBindingAdapter
 

Converters

如果綁定到 View 對象的變數需要在顯示之前進行格式化,轉換或更改,則可以使用 Converter 對象。
例如,使用顯示日期的 EditText 對象

<EditText
    android:id="@+id/birth_date"
    android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>

viewmodel.birthDate 屬性包含 Long 類型的值,因此需要使用轉換器對其進行格式化。
因為使用雙向表達式,所以還需要一個反向轉換器(inverse converter)讓 data binding library 知道如何將字符串轉換回數據類型,在本例中為 Long。
此過程通過將 @InverseMethod 註釋添加到其中一個轉換器並使此註釋引用反向轉換器來完成。如下

public class Converter {
    @InverseMethod("stringToDate")
    public static String dateToString(EditText view, long oldValue,
            long value) {
        // Converts long to String.
    }
    public static long stringToDate(EditText view, String oldValue,
            String value) {
        // Converts String to long.
    }
}

 

Infinite loops using two-way data binding

使用雙向數據綁定時,小心不要引入無限迴圈。
當用戶更改屬性時,將呼叫使用 @InverseBindingAdapter 註釋的方法,並將數值分配給 屬性。反過來,這將呼叫使用 @BindingAdapter 註釋的方法,這將觸發對使用 @InverseBindingAdapter 註釋的方法的另一個調用,依此類推。
因此,通過比較使用 @BindingAdapter 註釋的方法中的新値和舊值來打破可能的無限循環非常重要。
 

Two-way attributes

下表為平台預設的雙向綁定屬性表。

Class Attribute(s) Binding adapter
AdapterView android:selectedItemPosition
android:selection
AdapterViewBindingAdapter
CalendarView android:date CalendarViewBindingAdapter
CompoundButton android:checked CompoundButtonBindingAdapter
DatePicker android:year
android:month
android:day
DatePickerBindingAdapter
NumberPicker android:value NumberPickerBindingAdapter
RadioButton android:checkedButton RadioGroupBindingAdapter
RatingBar android:rating RatingBarBindingAdapter
SeekBar android:progress SeekBarBindingAdapter
TabHost android:currentTab TabHostBindingAdapter
TextView android:text TextViewBindingAdapter
TimePicker android:hour
android:minute
TimePickerBindingAdapter
分類
Android Uncategorized

Bind layout views to Architecture Components (綁定佈局到架構元件)

前言

AndroidX 函式庫包含架構元件(Architecture Components),可以使用它來設計健壯,可測試和可維護的應用程序。
Data Binding Library 可與架構元件無縫協作,進一步簡化 UI 的開發。
應用程序中的 layout 可以綁定到架構元件中的數據,這些數據可以幫助管理 UI 控制器生命週期並通知數據中的更改。
以下介紹如何將架構元件合併到應用程序,以進一步使用 Data Binding Library 的好處。
 

Use LiveData to notify the UI about data changes

可以使用 LiveData 物件作為數據綁定來源,以自動通知UI有關數據更改的訊息。更多的資訊可以參考 LiveData Overview
與實現 Observable 的物件(例如 observable fields)不同,LiveData 物件了解訂閱對象的生命週期。相關的好處可以參考 The advantages of using LiveData
在 Android Studio 3.1 或之後的版本可以使用 LiveData 取代 observable fields。
要將 LiveData 與綁定類別一起使用,需要指定生命週期所有者(lifecycle owner)以定義 LiveData 對象的範圍。如下,在綁定類別初始化之後指定了 Activity 本身為生命週期所有者。

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);
        // Specify the current activity as the lifecycle owner.
        binding.setLifecycleOwner(this);
    }
}

可以使用 ViewModel元件(如 Use ViewModel to manage UI-related data)將數據綁定到佈局。在 ViewModel 中,可以使用 LiveData 對象轉換數據或合併多個數據來源。
如下示範如何在 ViewModel 中轉換數據。

class ScheduleViewModel extends ViewModel {
    LiveData username;
    public ScheduleViewModel() {
        String result = Repository.userName;
        userName = Transformations.map(result, result -> result.value);
}

 

Use VeiwModel to manage UI-related data

Data binding library 和 ViewModel 可以無縫協作,以顯示數據並對其更改做出反應。
將 ViewModel 元件與 data binding library 一起使用,可以將 UI 邏輯從佈局移動到元件中,讓元件更易於測試。
Data binding library 可確保在需要時綁定和取消綁定數據來源。
更多的訊息可以參考 ViewModel Overview
要將 ViewModel 與 data binding library 一起使用,必須實體化繼承自 ViewModel 的元件,取得綁定類別的實體,並將 ViewModel 元件分配給綁定類別中的屬性。如下

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Obtain the ViewModel component.
        UserModel userModel = ViewModelProviders.of(getActivity())
                                                  .get(UserModel.class);
        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);
        // Assign the component to a property in the binding class.
        binding.viewmodel = userModel;
    }
}

在 layout 中,使用表達式將 ViewModel 元件的屬性和方法分配給相對應的視圖。

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />

 

Use an Observable ViewModel for more control over binding adapters

可以使用實現 Observable 的 ViewModel 來通知其他應用程序元件有關數據更改的信息,類似於使用 LiveData 對象的方式。
在某些情況下,可能更適合使用 ViewModel 元件來實現 Observable 介面而不是使用 LiveData 對象,即使沒有 LiveData 的生命週期管理功能。
使用實現 Observable 的 ViewModel 元件可以更好地控制應用程序中的綁定適配器。此模式可以在數據變化時更容易控制通知,允許指定自定義方法以在雙向數據綁定中設置屬性的值。
要實現可觀察的 ViewModel 元件,必須創建一個繼承 ViewModel 並實現 Observable 介面的類別。
當觀察者使用 addOnPropertyChangedCallback() 和 removeOnPropertyChangedCallback()方法訂閱或取消訂閱通知時,使用者可以提供自定義邏輯。
還可以提供在 notifyPropertyChanged()方法中屬性更改時執行的自定義邏輯。
以下示範如何實現可觀察的 ViewModel

/**
 * A ViewModel that is also an Observable,
 * to be used with the Data Binding Library.
 */
class ObservableViewModel extends ViewModel implements Observable {
    private PropertyChangeRegistry callbacks = new PropertyChangeRegistry();
    @Override
    protected void addOnPropertyChangedCallback(
            Observable.OnPropertyChangedCallback callback) {
        callbacks.add(callback);
    }
    @Override
    protected void removeOnPropertyChangedCallback(
            Observable.OnPropertyChangedCallback callback) {
        callbacks.remove(callback);
    }
    /**
     * Notifies observers that all properties of this instance have changed.
     */
    void notifyChange() {
        callbacks.notifyCallbacks(this, 0, null);
    }
    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes should be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    void notifyPropertyChanged(int fieldId) {
        callbacks.notifyCallbacks(this, fieldId, null);
    }
}

 

分類
Android Uncategorized

Binding adapters (綁定轉接器)

前言

Binding Adapter 主要用來呼叫適當方式來設定數值。一個例子為透過 setText 方法來設定屬性值,另一個例子為透過呼叫 setOnClickListener 方法來設定事件監聽器。
Data Binding Library 讓使用者指定調用方法來設定數值,客製化綁定邏輯,並使用適配器指定返回對象的類型。

Setting attribute values

每當綁定值發生更改時,綁定類別必須使用綁定表達式在視圖上調用 setter 方法。使用者可以允許 data binding library 自動決定哪個方法,顯式的聲明方法或提供自定義邏輯來選擇方法。
 

Automatic method selection

對於一個命名為 example 的屬性,data binding library 會嘗試尋找名稱為 setExample(arg) 的方法,其方法接受兼容的參數。
只有屬性的名稱和型別會被用於搜尋,屬性的名稱空間則不會被考慮。
例如,對於 android:text=”@{user.name}” 表達式,data binding library 會尋找一個 setText(arg) 方法,該方法的參數(arg)的型別為 user.getName 方法所回傳的。例如,若 user.getName 方法回傳的型態為 String,則 data binding library 會尋找 setText 方法,其方法接受的參數型態為 String。若 user.getName 方法回傳的型態為 int,則 data binding library 會尋找 setText 方法,其方法接受的參數型態為 int。
表達式必須回傳正確的型態,如有必要可以進行轉型的動作。即使沒有屬性符合給定的名稱,數據綁定仍然工作。之後,可以使用數據綁定為任何 setter 建立屬性。
例如,類別 DrawerLayout 沒有任何屬性,但有很多 setter。
下方的佈局自動使用 setScrimColor(int) 和 setDrawerListener(DrawerListener) 方法作為 app:scrimColor 和 app:drawerListener 屬性的 setter

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">

 

Specify a custom method name

有些屬性的 setter 並不符合名稱,在這種情況下一個屬性可以藉由 BindingMethods 註解去連結一個 setter。
註釋與類別一起使用,可以包含多個 BindingMethod 註釋,每個註釋方法一個註釋。綁定方法可以添加到應用程序中任何類別的註釋。如下方範例
android:tint 屬性和 setImageTintList(ColorStateList) 方法結合,而不是和 setTint 方法結合。

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

大多數情況下不需要重命名 setter。已使用名稱約定實現的屬性可自動查找相對應的方法。
 

Provide custom logic

有些屬性需要客製化綁定邏輯。例如 android:paddingLeft 屬性並沒有其相對應的 setter,代替的是 setPadding(left, top, right, bottom) 方法。
一個 static binding adapter 方法加上 BindingAdapter 註釋允許使用者自定義如何調用屬性的 setter。
Android framework 類別的屬性已經有 BindingAdapter 註釋。如下為 paddingLeft 屬性的 binding adapter

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

參數的型態相當重要,第一個參數決定與屬性相關連的視圖型別。第二個參數確定給定屬性的綁定表達式所接受的型別。
Binding adapter 相當有用於其他客製化的類型。如一個客製化的 loader 可以從 worker thread 被呼叫用來讀取圖像。
當發生衝突時,使用者自定義的 binding adapter 會覆蓋過原先的 adapter。
也可以有多個 adapter 用來接收多個屬性。如下

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.get().load(url).error(error).into(view);
}

如下,可以在 layout 使用 adapter。
注意 @drawable/venueError 參考到 App 中的資源。冒號包括住@{}的寫法為合法的綁定表達式。

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

注意 data binding library 會忽略自定義命名空間以進行匹配。
當 imageUrl 和 error 都被 ImageView 物件使用時,Adapter 將會被呼叫。
若想讓屬性其中之一被使用時就呼叫 adapter,可以設定 requireAll 為 false,如下

@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
  if (url == null) {
    imageView.setImageDrawable(placeholder);
  } else {
    MyImageLoader.loadInto(imageView, url, placeholder);
  }
}

Binding Adapter method 可以選擇在其處理程序中使用舊值。使用舊值和新值的方法應首先聲明屬性的所有舊值。如下

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

 
Event handler 只能與帶有一個抽象方法的接口或抽象類別一起使用。如下

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

 
在 layout 中使用這個 event handler,如下

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

 
當偵聽器具有多個方法時,必須將其拆分為多個偵聽器。
例如 View.OnAttachStateChangeListener 有 2 個方法為 onViewAttachedToWindow(View) 和 onViewDetachedFromWindow(View)。就有 2 個介面去區分它們的 attribute 和 handler。

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

 
因為更改一個偵聽器也會影響另一個偵聽器,所以需要一個適用於任一屬性或適用於兩者的適配器。
可以透過設定 requireAll 為 false 表示並不是每個屬性都必須指定給表達式。

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        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);
                    }
                }
            };
        }
        OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener,
                R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

上面的示例比正常情況稍微複雜一些,因為 View 使用 addOnAttachStateChangeListener()和 removeOnAttachStateChangeListener()方法而不是 OnAttachStateChangeListener 的 setter 方法。
android.databinding.adapters.ListenerUtil 類別有助於跟踪以前的偵聽器,以便可以在綁定適配器中刪除它們。
藉由在 OnViewDetachedFromWindow 和 OnViewAttachedToWindow 方法加入 @TargetApi(VERSION_CODES.HONEYCOMB_MR1),data binding library 只會在裝置運行為 Android3.1 或以上的版本產生 listener。
 

Object conversions

Automatic object conversion

從綁定表達式返回 Object 時,data binding library 會選擇用於設置屬性值的方法。Object 會被強制轉換為所選方法的參數類型。
在使用 ObservableMap 存儲數據的應用程序,此行為很方便,如以下示例所示

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

也可以使用 object.key 的方式在 Map 中參考數值,如上例的 @{userMap[“lastName”]} 可以使用 @{userMap.lastName} 來取代。
在表達式中的 userMap 會回傳一個數值,該數值將會被自動轉型為 setText(CharSequence) 方法的參數類型,該方法用來設定 android:text 屬型的値。
如果參數類型不明確,則必須在表達式中強制轉換返回類型。

Custom conversions

在某些情況下,特定類型之間需要自定義轉換。例如 android:background 需要 Drawable,但指定的顏色值是整數。
以下範例顯示需要 Drawable 的屬性,但是提供了一個整數

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

每當需要 Drawable 並返回一個整數時,int 應該轉換為 ColorDrawable。可以使用帶有BindingConversion 註釋的靜態方法完成轉換,如下所示

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

但是,綁定表達式中提供的值類型必須一致。不能在同一表達式中使用不同的類型,如下

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

 

分類
Android Uncategorized

Generated binding classes (產生綁定類別)

前言

Data Binding 會產生綁定類別(binding class),用來存取佈局變數(layout’s variable)和視圖(View)。
以下描述了如何建立及客製化綁定類別。
綁定類別將佈局變量與佈局中的視圖連結起來,綁定類別的名稱和 package 可以自行定義。所有的綁定類別都繼承自 ViewDataBinding 類別。
每個 layout file 都會有一個對應的綁定類別。預設,類別的名稱會根據佈局文件的名稱,將其轉換為 Pascal 大小寫並添加 Binding。
若 layout 的名稱為 activity_main.xml,則對應的綁定類別為 ActivityMainBinding。
此類別包含佈局屬性(如user variable)到佈局視圖的所有綁定內容,並知道如何為綁定表達式指定值。
 

Create a binding object

在對佈局進行填充之後,應該快速的創建綁定對象(binding object),以確保在綁定到佈局中具有表達式的視圖之前不會修改視圖層次結構。
將對象綁定到佈局的最常用方法是使用綁定類別上的靜態方法。
可以透過使用綁定類別的 inflate 方法來擴展視圖層次結構並將對象綁定到該層次結構。如下

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());
}

 
除了 LayoutInflater 對象之外,還有一個替換的 inflate 方法,它接受 ViewGroup對象,如下

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

如果使用不同的機制對佈局進行填充,則可以單獨綁定,如下

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時候沒辦法事先知道綁定類型。在這種情況下,可以使用 DataBindingUtil 類創建綁定,如下

View rootView = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bind(viewRoot);

若開發者是在 Fragment, ListView, RecyclerView adapter 中使用 data binding item
則可以使用綁定類別或 DataBindingUtil 的 inflate方法。如下

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

 

具有 ID 的視圖

Data Binding 會在綁定類別中建立一個不可變的欄位,該欄位會對應於 layout 檔案中每個具有 id 的 View。如下 Data Binding 會建立 firstName 和 lastName 欄位

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <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:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
        android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

Data Binding 會在單次傳遞中從視圖層次結構中提取具有 ID 的視圖。這個機制比為佈局中的每個視圖調用 findViewById 方法都要快。ID 對於 Data Binding 並不是必要的,但仍有些實體需要從 code 存取視圖。
 

Variables

Data Binding 會為每個宣告在 layout 中的變數建立存取方法(setter and getter)。如下,將會為 user, image, note 變數建立存取方法。

<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>


ViewStubs

與普通視圖不同,ViewStub 物件從一個不可見的視圖開始。
當它們被顯示或被明確告知要填充時,它們會通過填充另一個佈局來替換自己的佈局。
由於 ViewStub 基本上會從視圖層次結構中消失,因此綁定對像的視圖也必須消失以允許垃圾回收聲明。
因為視圖是不可變的,所以 ViewStubProxy 對象取代了生成綁定類中的 ViewStub,使您可以在 ViewStub 存在時訪問它,並在 ViewStub 填充時訪問視圖層次結構
當填充另一個佈局時,必須為該佈局建立綁定。因此 ViewStubProxy 必須監聽  ViewStub,並在需要時建立綁定。在同一時間內只能有一個監聽器存在,ViewStubProxy 予許開發者設定 OnInflateListener,該監聽器將在建立綁定之後被呼叫。
 

Immediate Binding

當變量或可觀察對象發生變化時,綁定將會被排程更改在下一幀之前。但是,有時必須立即執行綁定。要強制執行,可以使用 executePendingBindings 方法。
 
 

進階綁定

動態變數

動態變數適用於無法得知特定的綁定類別的時候。如當一個 RecyclerView.Adapter 對非特定的佈局進行操作時並不知道特定的綁定類別,但它還是必須在呼叫 onBindViewHolder 方法時指定綁定値。
在下面的範例中,RecyclerView 綁定的所有佈局都有個 item 變數。而 BindingHolder 物件具有 getBinding 方法,該方法可以回傳 ViewDataBinding 基本類別。

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

Data Binding 在 module package 會產生一個名為 BR 的類別,該類別包含用於數據綁定的資源的 ID。在上一個範例中 BR.item 是自動產生的。
 

背景執行緒

開發者可以在背景執行緒中更改數據模型(data model),只要它不是集合即可。Data Binding 會判斷每個變數/屬性以避免任何並發問題。
 

客製化綁定類別名稱

在預設情況下綁定類別的名稱是根據其相關佈局名稱而來,主要規則是將佈局名稱的底線去除並讓首字改為大寫,最後再加上 Binding,而綁定類別的位置會放置於 module package 的 databinding 資料夾下。 如佈局名稱為 contact_item.xml,其綁定類別為 ContactItemBinding。若 module package 為 com.example.my.app 則綁定類別的位置為 com.example.my.app.databinding。
可以透過 data 元素的 class 屬性來改變綁定類別的名稱或位置。
如下面的內容將會產生名為 ContactItem 綁定類別。

<data class="ContactItem">
…
</data>

也可以使用完整名稱來指定綁定類別的位置。如下
將產生 ContactItem 綁定類別,其位置為 com.example

<data class="com.example.ContactItem">
…
</data>

 

分類
Android Uncategorized

Splash Activity

1.編輯 value/styles.xml 加入以下內容

<resources>
...
    <style name="AppTheme.Launcher">
        <item name="android:windowBackground">@drawable/launch_screen</item>
        <!-- Optional, on Android 5+ you can modify the colorPrimaryDark color to match the windowBackground color for further branding-->
        <!-- <item name="colorPrimaryDark">@android:color/white</item> -->
    </style>
...
</resources>

其中 @drawable/launch_screen 目前還沒有,下一步製作。

2.在 drawable 新增 launch_screen.xml,內容如下

<?xml version="1.0" encoding="utf-8"?>
<!-- The android:opacity=”opaque” line — this is critical in preventing a flash of black as your theme transitions. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:gravity="center"
      android:src="@drawable/your_logo"/>
  </item>
</layer-list>

其中 @drawable/your_logo 就是你想顯示的 logo 圖示

3. SplashActivity

該 Activity 除了在 onCreate 作了特殊處理之外,並沒有其它不同。如下

public class LogoActivity extends Activity {
...
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    setTheme(R.style.AppTheme);
    super.onCreate(savedInstanceState);
  }
...

 

4. 在 AndroidManifest.xml 讓 LogoActivity 套用 AppTheme.Launcher

    <activity
      android:theme="@style/AppTheme.Launcher"
      android:name=".logo.LogoActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

 

分類
Uncategorized

work with observable data objects (使用可觀察數據對象) (Data Binding)

前言

可觀察性(Observability)是指一個物件具有當其數據發生變化時通知其他元件的能力。
Data Binding Library讓物件,欄位,集合具有可被觀察的能力
任何的 POJO 都可以使用於 Data Binding,但修改該物件時並不會讓 UI 也跟著更新。 Data Binding 可為數據對象(data object)提供在其數據更改時通知其他對象(稱為偵聽器)的能力。
可觀察類別(observable class)有 3 種類型,objects,fields,collections
當上列3種可觀察類別的物件綁定了 UI,且物件的屬性(property)發生變化時,UI 將會自動更新。
 

Observable fields

因為去建立實作 Observable 介面的類別需要一些額外工作,因此若開發者僅有少量的屬性其實是不值得的,在這種情況下可以使用一般性可觀察類別,如下

ObservableBoolean ObservableByte ObservableChar ObservableShort
ObservableInt ObservableLong ObservableFloat ObservableDouble
ObservableParcelable

 
Observable fields 是具有單一欄位的自包含可觀察物件,使用方式為建立一個 public final 屬性,如下

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

要存取數值,使用 set 和 get 方法如下

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

注意: Android Studio 3.1 或更高的版本可讓開發者以 LiveData objects 代替 observable fields,可提供額外的好處。參考 Use LiveData to notify the UI about data changes
 

Observable collections

有些 App 使用動態資料結構來持有數據,observable collections 為提供這些動態資料結構。
ObservableArrayMap 用於當鍵值為參考型別時特別有用,如下

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

在 layout 檔案中,map 使用如下

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
    android:text="@{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text="@{String.valueOf(1 + (Integer)user.age)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

ObservableArrayList 則適用於當鍵值為 integer 時,如下

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在 layout 檔案中,list 使用如下

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
    android:text='@{user[Fields.LAST_NAME]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

 

Observable objects

當類別實作了 Observable 介面便可提供註冊監聽器,該監聽器可讓想觀察的元件可取得通知。
Observable 介面具有加入和移除監聽器的機制,但開發者必須決定何時送出通知。為了讓過程更簡單,Data Binding Library 提供了 BaseObservable 類別,該類別實作了監聽器註冊機制。
當屬性發生變化時,實作 BaseObservable 的類別便負責通知。透過在 getter方法加入 Bindable 註釋,並在 setter 方法呼叫 notifyPropertyChanged 方法,如下

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

 
Data Binding 會在 module package 產生一個 BR 的類別,該類包含用於數據綁定的資源的 ID。
Bindable 註釋在編譯期間會產生一個 entity 到 BR 類別。
如果無法更改 data class 的 base class,則可以使用 PropertyChangeRegistry 實現 Observable 接口,以有效地註冊和通知監聽器。

分類
Uncategorized

Layouts and binding expressions (佈局和綁定表達式)(Data Binding)

1.簡介

表達式語言(expression language)允許開發者編寫處理視圖調度事件的表達式。
Data Binding Library 會自動生成視圖與數據對象綁定所需的類別(綁定類別)(binding class)。
數據綁定佈局文件(data binding layout files)和一般佈局文件不同。基本上會以 layout 標籤開頭,後面跟著 data 元素和 view 的元件。這個 view 元件代表非綁定部分的根節點。如下範例所示

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <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}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

在 data 元素的 user 變數代表可在這個佈局中使用的屬性。

<variable name="user" type="com.example.User" />

表達式透過 @{} 語法將變數和佈局結合,如下 TextView 將被設定給 user 變數的 firstname 屬性。

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

Note:
因為佈局表達式(layout expression)無法進行單元測試,因此應該保持小而簡單,開發者可以透過 binding adapter 保持簡潔。

2.Data Object(資料物件)

假設有個 User 類別如下

public class User {
 
  public final String firstName;
  public final String lastName;
 
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

這種類型的物件具有永不改變的數據,該數據通常會讀取一次且不會更改。也可以使用遵循一組約定的對象,例如 Java 中的訪問器方法,如以下

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

從數據綁定的角度來看,這兩個類別是相同的。
以下方表達式而言

<TextView
   …
   android:text="@{user.firstName}"
/>

等同於存取第一個類別的 public final String firstName 變數,也等同於呼叫第二個類別的 getFirstName 方法
以編程方式來呈現如下

TextView firstName = (TextView)findViewById(….)
firstName.setText(user.firstName);
or
firstName.setText(user.getFirstName());

3.Binding Data(綁定類別)

Data Binding 基本上會為每個佈局文件產生綁定類別(binding class)。
預設綁定類別的名稱會根據佈局文件的名稱建立,建立方式為將其轉換 Pascal 大小寫並添加 Binding 後綴。
舉例來說若佈局文件名稱為 activity_main.xml,則相對應的綁定類別為 ActivityMainBinding。
綁定類別將會持有對應佈局文件的所有綁定內容(如 user 變數)。並知道如何為綁定表達式指定數值。
建立綁定類別的推薦方式為在擴展佈局時建立。如下

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

在執行 App 時,將在 UI 中顯示 Test。或可以使用 LayoutInflater 獲取視圖,如下:

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

若開發者在 Fragment,ListView 或 RecyclerView adapter 內使用數據綁定,則可以使用綁定類別或 DataBindingUtil 類別的 inflate 方法,如下:

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

4.Expression Language(表達式語言)

一般特性

數學 字串連接 邏輯 二進制 一元
+ – / * % + && || & | ^ + – ! ~
移位 比較 測試型態 分組 文字
>> >>> << == > < >= <= Instanceof () character, String, numeric, null
轉型 方法呼叫 欄位存取 陣列存取 三元運算符
Cast Method calls Field access [] ? :

範例

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

目前不支援的運算符

This Super New Explicit generic invocation
(顯式通用調用)

空結合運算符(Null coalescing operator) ??

?? 表示為空結合運算符,若該運算符的左邊為 null 則選擇運算符的左邊,若該運算符的左邊不為 null 則選擇運算符的右邊。如下

android:text="@{user.displayName ?? user.lastName}"

等同於

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

屬性參考(Porperty Reference)

表達式可以參考類別的屬性,也可以使用於 fields, getters, ObservableField objects

android:text="@{user.lastName}"

避免空指標異常(avoiding null pointer exception)
綁定類別會自動檢查空指標異常,並提供預設值。
若表達式 @{user.name} 若 user 為 null 則提供預設值 null (字串)。
若表達式 @{user.age } 且 age 的型別為 int,若 user 為 null 則提供預設值0。

集合(Collection)

一般的集合,如 array, list, map 都可以透過 [] 運算符來操作。如下

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="Map<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

Note:
也可以透過 object.key 來參考 map 的 元素。
如 android:text=”@{map[key]}” 等同於 android:text=”@{map.key}”

字串

可以使用單引號表示屬性值,或是使用雙引號表示字串。

android:text='@{map["firstName"]}'

也可以使用雙引號表示屬性值,但在這種情況下就必須使用 ` 表示字串

android:text="@{map[`firstName`]}"

資源

可以使用以下表達式來存取資源

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

格式化字串和複數可以提供參數來計算

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

若複數需要多個參數,必須全部傳遞它們

Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"

以下為資源對應的表示方式

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

5.Event handling(事件處理)

Data Binding 可以透過表達式處理從視圖發送的事件(如 onClick() 方法)。
事件屬性名稱由監聽器的方法名稱決定,但有一些例外。如 View.OnClickListener 有方法為 onClick(), 則該事件的屬性為 android:onClick
除了 android:onClick 以外還有一些點擊事件的特殊項目。這些特殊項目必須使用不同的屬性來避免衝突。如下

Class Listener setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomOut

可以使用以下機制來處理事件
1.方法引用(method references)
在表達式中可以引用符合偵聽器方法簽名的方法。
當表達式為方法引用時,Data Binding 會包裝方法引用(method reference)和所有者物件(owner object)在監聽器中並將監聽器設定到目標視圖。
若方法引用為 null 則 Data Binding 不會建立監聽器並設定 null
2.監聽器綁定
當事件發生時使用 lambda 表示式。
Data Binding 會建立監聽器並設定到目標視圖上。當處理事件時,監聽器將會執行 lambda 表示式。

方法引用(method reference)

事件可以直接綁定到方法,類似於 android:onClick 可以指定給 Activity 的方法。
與 View onClick 相比,主要優點是表達式在編譯時期處理,因此如果該方法不存在或其簽名不正確,會收到編譯錯誤。
方法引用和偵聽器綁定的主要區別在於偵聽器實現是在綁定數據時建立的,而不是在觸發事件時建立。
若想在事件發生時再執行表達式,選擇監聽器綁定較好。
使用普通綁定表達式(normal binding expression)將事件分配給其處理者,其值為要調用的方法名稱。如下類別

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

表達式可以綁定視圖的點擊監聽器(click listener)到 onClickFriend 方法,如下

<?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>

Note:
表達式方法的簽名必須和監聽器對象方法的簽名完全相同。

監聽器綁定(listener binding)

監聽器綁定為當事件發生時才執行綁定的表達式。類似於方法引用,但可以運行任意數據綁定表達式。該特性適用於 Android Gradle 2.0 或以上的版本。
在方法引用中,方法的參數必須和事件監聽器的參數完全相同。
監聽器綁定則是方法的回傳值和監聽器的回傳值必須相同(除非是回傳 null)。
如下類別

public class Presenter {
    public void onSaveClick(Task task){}
}

可綁定點擊事件到 onSaveClick 方法,如下

<?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>

在表達式使用 callback 時,Data Binding 會自動建立需要的監聽器並註冊到事件。當視圖發送事件時,Data Binding 會執行相對的表達式。當表達式執行時可以確保空指針(null)和執行緒安全。
在上面的示例中,我們未定義傳遞給 onClick(View)的視圖參數。
監聽器綁定提供了 2 個選擇來指定監聽器的參數。開發者可以忽略所有的參數或命名所有的參數。
若命名了參數,則開發者可以在表達式中使用它。如上面的範例可以改為

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

若想使用表達式的參數,如下

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

可以使用 lambda 表達式提供超過 1 個的參數。

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,則表達式必須回傳同類型的型態,如下

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果表達式執行結果為 null,則數據綁定將返回該類型的默認值。例如,引用類型為 null,int 為 0,布林值為 false 等。
若在表達式使用三元運算符則可使用 void,如下

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

避免複雜的監聽器

監聽器表達式可以讓代碼非常容易了解。若表達式過於複雜也會使佈局難以閱讀和維護。
這些表達式應該像 UI 的可用數據傳遞給回調方法一樣簡單並實現業務邏輯在監聽器表達式呼叫的回調方法中。

6. Imports, variables, and includes

Imports 用來在 layout 中可以參考到類別,variable 用來描述在表達式使用的屬性, includes 可在整個應用中重複使用複雜的佈局。
 

Imports

可在 layout file 簡單的參考到類別,必須寫在 data 元素裡面。如下 import View 類別到 layout file 中。

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

Import View 類別之後便可在表達式中參考它。如下在表達式中使用 View 類別的  VISIBLE 和 GONE 常數。

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

 型別別名(Type aliases)

若類別名稱發生了衝突,當中的類別便可重命名為別名。如下將 com.example.real.estate 的 View 類別重新命名為 Vista。

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>

Import other classes

import 的型別可以在變數或表達式中用來當作型別參考,如下

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
</data>

注意: Android Studio 還未完全支援 import,因此自動完成的功能還無法使用在 IDE 中。
Import 的型別也可以用於轉型。如下將 connection 屬性轉型成 User

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

在表達式中也可以使用 import 型別來參考靜態變數或方法,如下 import MyStringUtils 並使用其 capitalize 方法。

<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"/>

注意 java.lang.* 會自動 import。
 

Variables

可以在 data 元素中使用多個 variable元素,每個 variable 元素代表可能會使用在表達式的屬性。如下宣告了 user, image, note

<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>

變數的型別會在編譯時期檢查,因此若變數實作了 Observable 或 observable collection 將會被反射到型別上。
若變數是基本類別或介面且未實作 Observable ,則該變數是無法被觀察(observable)
當不同佈局文件用於各種配置(橫向或縱向)時,這些變量會被組合。佈局文件之間不可存在互相衝突的變數名稱定義。
每個變數都有 setter 和 getter 方法定義在綁定類別中。變數將會使用預設數值,如整數為 0,布林為 false,參考型別則為 null。
有個特別變數為 context 會自動產生以便用於表達式,該變數的數值為根視圖(root view) getContext 方法的回傳值。

Includes

透過使用 App 的命名空間和屬性的名稱,變數可以從包含的佈局傳遞到佈局綁定中。如下從 name.xml 和 contact.xml 佈局中 include user 變數。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <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>
</layout>

注意 : 不支援從 merge 元素使用 include 動作。如下不支援該用法

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>
分類
Uncategorized

finaldb 和 Serializable 合併使用問題

問題描述:

若有個物件想透過 finalDB 儲存但又實作了 Serializable 介面。
當去存取該物件時,會出現”serialVersionUID has type long, got null” 相關問題。
發生問題的 class 如下

@Table(name = "GoodItem")
public class GoodItem implements Serializable{
    private static final long serialVersionUID = -6588468312284378785L;
    @Id
    private String id;
...

顯示的錯誤訊息如下

java.lang.IllegalArgumentException: field xxx.serialVersionUID has type long, got null
at java.lang.reflect.Field.set(Native Method)
at net.tsz.afinal.db.table.Property.setValue(Property.java:70)
at net.tsz.afinal.db.sqlite.CursorUtils.getEntity(CursorUtils.java:45)
at net.tsz.afinal.FinalDb.findAllBySql(FinalDb.java:574)
at net.tsz.afinal.FinalDb.findAll(FinalDb.java:528)
...

基本上就是 serialVersionUID 和 finalDB 發生衝突。
 

解決方法:

將 serialVersionUID 加上  @Transient 註解來解決,如下

@Table(name = "GoodItem")
public class GoodItem implements Serializable{
    @Transient
    private static final long serialVersionUID = -6588468312284378785L;
    @Id
    private String id;
...

 
 

分類
Uncategorized

Data Binding 前置準備

版本限制

Data Binding 函式庫提供靈活性和廣泛的兼容性,目前可運行在 Android 4.0( API Level 14)以及更高的版本,以及建議使用最新的 Android Gradle 插件,Data Binding 可以運作在 1.5.0 及更高的版本
 

相依性

要啟用 Data Binding 函式庫,必須在 app module 的 build.gradle 檔案加入以下內容

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

 

Android Studio 支援特性

Android Studio 提供以下關於 Data Binding 的特性
1.語法高亮
2.標示語法錯誤
3.XML 代碼自動完成
 

新型資料綁定編譯器(data binding compiler)

Android Gradle 3.1.0-alpha06 版本包含新型資料綁定編譯器,該編譯器能更快速的產生綁定類別(binding class)。
使用舊型資料綁定編譯器可能會產生多個錯誤訊息並提示找不到綁定類別,而使用新型資料綁定編譯器可避免這些狀況。
透過在 gradle.properties 檔案設定以下內容以啟用新型資料綁定編譯器

android.databinding.enableV2=true

 
Note:
注意 Android Studio 3.1 還未完全支援新型資料綁定編譯器,而 Android Studio 3.2 預設已支援新型資料綁定編譯器

分類
Uncategorized

Data Binding (Android Jetpack)

什麼是 Data Binding

Data Binding 是一個函式庫,允許開發者使用聲明性格式(而不是以編程方式)將佈局 (layout) 中的 UI 元件綁定到應用程序中的數據來源。

使用編程方式綁定

佈局元件 (layout) 通常會定義在 Activity 內並呼叫相關的 UI 框架方法。
如下透過呼叫 findViewById 找到 TextView 並綁定到 viewModel 的 userName 屬性

TextView textView = findViewById(R.id.sample_text);
textView.setText(viewModel.getUserName());

使用 Data Binding 綁定

以下則是使用 Data Binding 直接在佈局檔案指定文字給 widget,這種方式可以取代上面範例的 Java code

<TextView
    android:text="@{viewmodel.userName}" />

透過在佈局檔案中綁定元件的方式可以移除許多需要在 Activity 呼叫的 UI 框架方法。讓  Activity 更簡潔並容易維護,也能改善效能,避免記憶體洩漏及 Null Pointer Exception。

Using the Data Binding Library

1.前置準備

2.佈局(layout)和綁定表達式(binding expressions)

3.使用可觀察資料物件(observable data objects)

4.產生綁定類別(binding classes)

5.綁定轉接器(binding adapters)

6.綁定佈局到架構元件(bind layout views to architecture components)

7.雙向綁定(Two-way data binding)