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




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

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

 

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

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

 

Orignal From: Binding adapters (綁定轉接器)

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>

 

Orignal From: Generated binding classes (產生綁定類別)

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>

 

Orignal From: Splash Activity

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 接口,以有效地註冊和通知監聽器。

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

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

Layouts and binding expressions


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>




 

Orignal From: Layouts and binding expressions (佈局和綁定表達式)

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

 

解決方法:


將 serialVersionUID 加上  @Transient 註解來解決,如下
@Table(name = "GoodItem")  
public class GoodItem implements Serializable{

@Transient
private static final long serialVersionUID = -6588468312284378785L;

@Id
private String id;
...

 

 

Orignal From: finaldb 和 Serializable 合併使用問題

Data Binding 前置準備

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 預設已支援新型資料綁定編譯器。

Orignal From: Data Binding 前置準備

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.綁定佈局到架構元件(architecture components)

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

 

Orignal From: Data Binding (Android Jetpack)

Lifecycle-Aware Components

什麼是 Lifecycle-aware Components?


Lifecycle-ware components(生命週期感知元件)包含在 android.arch.lifecycle 套件中,基本上就是當元件(Activity or Service)的生命週期發生變化時,生命週期元件即會自動收到通知並觸發相對應的動作。

 

為什麼需要生命週期感知元件?


在此之前若開發者需要根據元件的生命週期執行相對應的動作時都必須寫在元件的對應方法中。最常見的例子為旋轉螢幕時需要儲存資料,不是寫在 onPause 方法就是寫在 onSaveInstanceState 方法。

但這種寫法最直接的缺點就是增加 Activity 的大小,讓 Activity 變得越來越大。

生命週期感知元件可以根據 Activity 或 Fragment 的當前生命週期狀態自動呼叫其行為。

 

Lifecycle


Lifecycle 是一個類別,擁有關於另一個元件(Activity or Fragment)生命週期狀態的資訊。
Lifecycle 使用 2 個列舉來追蹤其相關連元件的生命週期狀態:

1.Event(事件)

Event 透過 Framework 和 Lifecycle class 發送,Event 會對應於 Activity 或 Fragment 的回調事件(callback events)。

2.State(狀態)

相關連元件的當前狀態。

LifecycleOwner


LifecycleOwner 為只有一個方法的介面,該方法必須回傳 Lifecycle。

在 Support Library 26.1.0 已將 Activity 和 Fragment 繼承 LifecycleOwner 並寫好回傳 Lifecycle 的實作部分,也就是說 Activity 和 Fragment 就是 LifecycleOwner。

要自動感知 Activity 的生命週期,首先呼叫 getLifecycle 方法取得 Lifecycle,接著再呼叫 Lifecycle 的 addObserver 方法讓 LifecycleObserver 訂閱 Lifecycle。

基本上就是透過 LifecycleOwner 提供 Lifecycle,接著讓 LifecycleObserver 訂閱
Lifecycle,如此 LifecycleObserver 就能感知 Lifecycle 的變化。

 

LifecycleObserver


LifecycleObserver 的用途就是用來跟蹤繼承 LifecycleOwner 的元件,透過在方法上加上註解的方式,可以自動取得目標元件的相關生命週期變化。

一個最簡單用來感知 Activity 的 LifecycleObserver 如下
import android.arch.lifecycle.Lifecycle;  
import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.OnLifecycleEvent;
import android.content.Context;
import android.util.Log;

public class LifecycleMainActivityObserver implements LifecycleObserver {

private static final String TAG = LifecycleMainActivityObserver.class.getSimpleName();

@OnLifecycleEvent(Event.ON_START)
public void lifecycleStart() {
Log.d(TAG, "detect lifecycle on start");
}

@OnLifecycleEvent(Event.ON_CREATE)
public void lifecycleCreate() {
Log.d(TAG, "detect lifecycle on create");
}

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void lifecycleResume() {
Log.d(TAG, "detect lifecycle on resume");
}

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void lifecyclePause() {
Log.d(TAG, "detect lifecycle on pause");
}

@OnLifecycleEvent(Event.ON_STOP)
public void lifecycleStop() {
Log.d(TAG, "detect lifecycle on stop");
}

@OnLifecycleEvent(Event.ON_DESTROY)
public void lifecycleDestroy() {
Log.d(TAG, "detect lifecycle on destroy");
}


@OnLifecycleEvent(Event.ON_ANY)
public void lifecycleChange() {
Log.d(TAG, "detect lifecycle change");
}
}

註解 OnLifecycleEvent(Event.ON_CREATE) 就表示當目標元件處於 create 狀態時,該方法便會被呼叫。

接著只要在目標元件的 onCreate 方法初始化 observer,並呼叫 addObserver() 即可
public class LifecycleMainActivity extends AppCompatActivity {  

private LifecycleMainActivityObserver mLifecycleMainActivityObserver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lifecycle_main_activity);

mLifecycleMainActivityObserver = new LifecycleMainActivityObserver();
getLifecycle().addObserver(mLifecycleMainActivityObserver);
}
}

最後 LifecycleMainActivityObserver 的方法便會對應 LifecycleMainActivity 的生命週期改變自動被呼叫。

 

關於生命感知週期元件的最佳實踐


1.保持 UI Controllers(Activity or Fragment)盡量精簡,它們不該存取自己的資料,而應該交由 ViewModel 來執行,並透過 LiveData 將資料的變更反射回 View。

2.嘗試實作數據驅動的 UI,其中 UI Controller 負責在數據更改時更新視圖,或將用戶操作通知給 ViewModel。

3.應該將數據邏輯放到 ViewModel,把 ViewModel 當作 UI 和其他元件的連接器且 ViewModel 不應該去直接獲取數據(如本地端資料庫,遠端網路)。而是透過其它元件來取得數據,再返回給 UI controller。

4.使用 Data Binding 來建立 View 和 UI Controller 之間的簡潔介面,可讓 View 更具聲明性並盡量的減少在 Activity 和 Fragment 需要寫的代碼。也可以考慮使用 Butter Knife 來避免樣版代碼以及建立良好的抽象。

5.若 UI 非常複雜可以考慮建立 Presenter 來處理UI的變化。

6.不要在 ViewModel 中引用 View 或 Activity context,否則可能造成記憶體洩漏。

 

生命感知週期的使用案例


1.動畫,當 App 位於後台時停止動畫,返回前台時恢復動畫

2.網路連接,當 App 位於後台時暫停網路,返回前台時恢復網路

3.視頻緩衝,當 App 啟動時盡快啟動緩衝,但直到App完全啟動時再播放視頻。可在App銷毀時終止視頻緩衝。

4.位置更新,當 App 位於後台時使用粗粒度位置更新,返回前台時使用細粒度位置更新。

 

Orignal From: Lifecycle-Aware Components

Android Architecture Components

什麼是 Android Architecture Components


Android Architecture Components(AAC) 是函式庫的集合,可以幫助開發者寫出穩健,可測試,維護性高的 App。

 

AAC 包含了以下函式庫






Data Binding
Lifecycles
LiveData
Navigation
Paging

將可觀察資料綁定到UI元件
管理Activity和Fragment 生命週期
當資料改變時通知視圖(View)
處理App導航功能
循序的從數據來源讀取資料

Room
ViewModel
WorkManager
 
 

流暢存取SQLite
以生命週期方式管理UI相關資料
管理後台任務
 
 



 

也可以看到 AAC 涵蓋了App的整個架構主體。

基本上有以下4個要點

1. lifecycle-aware components 管理 Activity 或 Fragment 的生命週期。


可以避免記憶體洩漏(memory leaks),減少處理配置變更(configuration changes)的成本,更簡易的讀取資料到 UI。

2. LiveData 建立資料物件,當資料改變時會自動通知視圖(Views)。


可以套用 Observer Pattern 的觀念,把 LiveDate 當作 Subject,視圖當作 observer。
視圖會訂閱 LiveData,因此當 LiveData的資料發生變化時,視圖會自動收到通知。

3. ViewModel 保存 UI 相關的資料,讓 App 旋轉螢幕時不會銷毀資料。


ViewModel 就是擔任 MVVM Pattern 的 VM 角色,透過隔離視圖邏輯和數據層邏輯,方便撰寫測試,專注業務邏輯。

4. Room 能更快速的存取資料庫(SQLite)並可搭配 RxJava 協同使用。


Room 為 ORM 的一種,和其它流行的框架類似能夠大量簡化 SQL 語法。

 

Orignal From: Android Architecture Components

Android Jetpack (噴射背包!!!) 簡介

什麼是 Android Jetpack?


Android Jetpack 是一系列軟體元件的組合,透過應用這些元件可以幫助您寫出最佳實踐,避免樣板代碼並簡化複雜任務,讓開發者專注於需要關心的代碼。

Jetpack 包含在 androidx.* 的套件名稱,沒有和任何平台 API 綁定,它向後相容並可頻繁更新,代表開發者可以隨時使用最新最好的版本。

 

使用 Jetpack 的好處


1.加速開發


Jetpack 元件可單獨或組合使用,同時利用 Kotlin 提供的特性來加速開發

2.消除樣板代碼


Jetpack 特別用來管理複雜的行為,如後台任務,導航,生命週期。讓您專注在需要關心的部分

3.建立高品質,穩固的 App


Jetpack 可以減少崩潰,減少記憶體洩漏並提供向後兼容性

 

Android Jetpack 元件組


主要有 4 個分類,每個分類都包含不同數量的元件,各個元件提供特定的功能,各為 Fundation , Architecture , Behavior , UI

1.Foundation


提供向後兼容,支援 Kotlin,測試相關功能




AppCompat
Android KTX
Multidex
Test

為舊版 App 提供向後兼容性
支援 Kotlin 語言
支援多 DEX 檔案
Android 測試框架,提供單元測試和 UI測試



 

2.Architecture


提供建立穩固,可測試,可維護性高的 App






Data Binding
Lifecycles
LiveData
Navigation
Paging

將可觀察資料綁定到 UI 元件
管理 Activity 和 Fragment 生命週期
當資料改變時通知視圖(View)
處理 App 導航功能
循序的從數據來源讀取資料

Room
ViewModel
WorkManager
 
 

流暢存取SQLite
以生命週期方式管理 UI 相關資料
管理後台任務
 
 



 

3.Behavior


讓 App 可和標準 Android 服務集成,如 Notification,Permission,Sharing,Assistant




DownloadManager
Media & playback
Notifications
Permissions
Sharing
Slices

排程和管理大量下載
播放媒體(包含Google Cast)
提供通知功能並可向後兼容
檢查和要求 App 權限
用於 Action bar 的共享操作
可在 App 之外顯示資料的 UI



 

4. UI


提供 widgets 讓 App 使用起來不僅簡單且有趣






Animation & transitions
Auto
Emoji
Fragment
Layout

用來移動或轉換widget
幫助開發Android Auto 元件
可在舊版本上使用最新的Emoji功能
可元件化的基本單位
關於App版面配置

Palette
TV
Wear OS By Google
 
 

從調色板中提取有用訊息
幫助開發Android TV 元件
幫助開發Android Wear 元件
 
 



 

 

 

Orignal From: Android Jetpack (噴射背包!!!) 簡介

ViewModel 基本紀錄(Android Architecture Component)

Overview


ViewModel 為 Android Architecture Component 其中一部分,主要的用途為以生命週期方式來儲存與管理UI相關數據。好處為當配置更改(螢幕旋轉)之後能夠予許數據繼續存在

以架構來說通常是作為 MVVM 的 VM(ViewModel) 角色來呈現, 透過把和 View 不相關的邏輯提取出來放到 VM,一方面可以減輕 View 的職責,一方面提高其它模組內聚力,也讓測試更容易撰寫。

Dependency


參考官網

實作 ViewModel


實作方式非常簡單,只要繼承 ViewModel 即可。通常 ViewModel 會和 LiveData 同時使用關於 LiveDate 請參考這裡
如同 Overview 提到的 ViewModel 可以在配置改變時自動保留數據,因此應該盡量把數據部分放到 ViewModel 而不是 Actvity 或 Fragment
public class MyViewModel extends ViewModel {  

private static final String TAG = MyViewModel.class.getSimpleName();

}

Note:通常會和 LiveData一起使用,但這裡為了方便說明,先不加入LiveData。

Using in Activity


public class ViewModelMainActivity extends AppCompatActivity {  

private static final String TAG = ViewModelMainActivity.class.getSimpleName();

private MyViewModel mViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_model_main_activity);

mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
Log.d(TAG,"mViewModel.toString():"+ mViewModel.toString());
}
}

第12行透過 ViewModelProviders.of(this).get(MyViewModel.class); 初始化 mViewModel。
第13行用來測試旋轉螢幕後是否為同一個 ViewModel 實體。接著你可以開始旋轉螢幕了。
Note :
1.注意在 ViewModel 內絕不可引用 View, Lifecycle, 或是任何本身還會引用 activity context 的類別若 ViewModel 內需要使用 context 就繼承 AndroidViewModel
2.當呼叫 ViewModelProviders.of(this).get(MyViewModel.class); 之後,ViewModel 就會保存在記憶體中,直到它的作用域消失。也就是當ViewModelMainActivity 為 finish 狀態時,ViewModel 即會消失。

LiveDate 和 ViewModel 共用


LiveData 的用途為提供 observer 可以觀察本身所擁有的數據,而 ViewModel 則是提供 View 數據並可自動處理配置變化所引發的事件。
這 2 者共同使用對於 View 來說便可發揮極大的功效。
public class MyViewModel extends ViewModel {  

private static final String TAG = MyViewModel.class.getSimpleName();

private MutableLiveData<List<User>> mUsers;

public LiveData<List<User>> getUsers() {
if (mUsers == null) {
mUsers = new MutableLiveData<>();
loadUsers();
}
return mUsers;
}

private void loadUsers() {
...
}
}

 

Scope of ViewModel


從圖中可以看到當 Activity 在 onCreate 時,ViewModel 就跟著產生。而當 Activity finish 之後,呼叫完 onDestroy 方法後,ViewModel 就跟著消失。

在 Fragment 之間分享數據


若同一個 Activity 擁有 2 個 fragment , 也可以使用 ViewModel 讓這 2 個 fragment 共享數據。
public class SharedViewModel extends ViewModel {  
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

public void select(Item item) {
selected.setValue(item);
}

public LiveData<Item> getSelected() {
return selected;
}
}

public class MasterFragment extends Fragment {  

private SharedViewModel model;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}

public class DetailFragment extends Fragment {  

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}

注意這 2 個 fragment 都使用了 getActivity 來取得 ViewModelProvider,因此,這 2 個 fragment 都接受相同的 SharedViewModel 實體。

這個做法有以下優點
1. Activity 不需要做任何事,也不需要了解 Fragment 之間的溝通。
2. Fragment 不需要彼此了解。若其中一個 Fragment 發生問題,另一個 Fragment 繼續正常工作。

 

 

 

 

 

 

 

Orignal From: ViewModel 基本紀錄(Android Architecture Component)

LiveData 基本紀錄(Android Architecture Component)

LiveData 為 Android Architecture Component 其中之一,主要用途為持有可觀察數據(observable data),並讓有註冊且為活躍狀態的觀察者自動收到更新。

若了解或實作過Observer Pattern(觀察者模式)會很容易了解,可以把LiveData看作Subject(主題)也就是資料的來源。對Subject有興趣的observers可以訂閱主題,訂閱完成後當主題內的數據發生變化時,observers便會自動收到通知。

基本上 Observer Pattern 是用來取代 polling 的解決方案。關於Observer Pattern 可以參考這篇

回過頭來,首先看看LiveData的OverView(這裡是官網介紹)

首先LiveData是一種擁有可觀察數據的類別(看字面不好懂,直接看實作吧) e.g.,
  private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();

因為LiveData為abstract class,通常在實作上會以MutableLiveData(LiveData's subclass)取代LiveData。角括號可以傳入任何型別,包含Collections,list等等。

LiveData最大的特色為它具有生命週期感知(Lifecycle-aware)能力,該能力可以讓LiveData得知其他元件(Activity, Fragment, Service)的生命週期。
因此可以避免很多必須手動操作的情況,如螢幕旋轉時需要儲存數據,UI和數據之間的同步,memoey leak等等。

如何使用LiveData


1.建立LiveData實體並持有特定類型的數據。這通常在ViewModel類別內實作

2.建立Observer物件並定義onChanged方法,該方法內容用來描述當LiveData持有的數據改變時所作的事。你通常會在UI Controlller內(Activity, fragment)建立Observer物件

3.使用observe方法連結Observer物件和LiveData物件。observer方法需要傳入LifecycleOwner物件,這讓Observer物件訂閱LiveData物件並讓LiveData可以通知改變。你通常會在UI Controller內(activity, fragment)連結Observer物件

Note:
你可以藉由observeForever(Observer)方法來註冊一個observer而不需要結合LifecycleOwner。

在這種情況下observer會被當作永遠都是活躍的,因此總是會收到改變的資訊。反之,可以使用removeObserver(Observer)來移除observer

當LiveData的資料被更新之後,所有的已註冊且相關連的LifecycleOwner為活躍的observers會收到通知。

Example

1.Create LiveData objects


LiveData可以包含任何類型的數據,如List等等。
LiveData通常儲存在ViewModel內並透過getter方法來存取。
public class NameViewModel extends ViewModel {  

private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();

public MutableLiveData<String> getCurrentName() {
return mCurrentName;
}
}

Note:確保LiveData儲存在ViewModel而不是activity或fragment

2.Observe LiveData objects


在大多數的情況下,onCreate方法為初始觀察LiveData位置。

在一般的情況下LiveData只在數據有更新時且觀察者為活躍的狀態才發送通知。但有2個例外,第1為當觀察者從非活躍狀態轉為活躍狀態時會收到通知。
第2為當觀察者是第2次從非活躍狀態轉為活躍狀態,則只有在自上次活動狀態以來該值發生變動時才會收到更新。
(也就是說當觀察者第1次從非活躍轉成活躍一定會收到更新。若是第2次從非活躍轉成活躍,且必須儲存的數據發生變化才會收到更新)
public class LiveDataMainActivity extends AppCompatActivity {  

private NameViewModel mViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.live_data_main_activity);

mViewModel = ViewModelProviders.of(this).get(NameViewModel.class);

Observer<String> nameObserver = new Observer<String>() {

@Override
public void onChanged(String newName) {
mName.setText(newName);
}
};

mViewModel.getCurrentName().observe(this, nameObserver);
}
}

 

3.Update LiveData objects


LiveData可以透過setValue和postValue方法來改變數據,2者的差別為setValue可用於main Thread,postValue可用於worker thread
String newName = "john doe";  
mViewModel.getCurrentName().setValue(newName);

因此當setValue呼叫之後,所有已註冊且為活躍的observer便會觸發onChanged方法

 

Orignal From: LiveData 基本紀錄(Android Architecture Component)

Gson 搭配 Gsonformat 基本使用紀錄

Gson 為 Google製作,可將JSON內容和java object互相轉換的library。

對我來說最大的用途就是可直接將Server回傳的json訊息直接轉換成java object,而不用再一一寫對應的 JsonObject。

而GsonFormat為 android studio 的外掛,用途是當你取得json內容之後,可以幫你產生對應的java object。

兩者搭配起來就可以方便的處理Server回傳訊息。

首先是Gson的部分:
這裡為Gson github連結,相關的安裝和使用都有介紹。

1.Gson Dependency
在 module 的 build.gradle 加入
dependencies {  
...
implementation 'com.google.code.gson:gson:2.8.5'
}

2.Using in code
2-1. Json to java object
首先假設有個User 類別為對應的 java object
public class User {  

private String name;
private int age;
private String sex;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}
}

2-2. java object to json
(就是將已存在的java物件轉成json 格式的string)
  @Test  
public void test_UserToJSON() {
User foxx = new User();
foxx.setAge(99);
foxx.setName("Foxx");
foxx.setSex("male");

Gson gson = new Gson();
String result = gson.toJson(foxx, User.class);
System.out.println("userToJSON:" + result);
assertEquals("{\"name\":\"Foxx\",\"age\":99,\"sex\":\"male\"}", result);
}

第9行即透過 gsong.toJson將 java object轉成 json string

2-3. json to java object
  @Test  
public void test_JSONToUser() {
Gson gson = new Gson();
String source = "{\"name\":\"Foxx\",\"age\":99,\"sex\":\"male\"}";
User target = gson.fromJson(source, User.class);
assertNotNull(target);
}

第5行透過gson.fromJson將json string 轉成 java object

 

接著是GsonFormat
1.Install GsonFormat
安裝方式和一般的android studio 外掛相同。
開啟 android studio -> File -> Settings -> Plugins -> Browse repositories
->輸入 gsonformat -> install -> 安裝完成後 重開 android studio

2.How to use
2-1.首先手動建立對應的空class(只有類別名稱無其他內容),以上面的User為例,我們就先建立一個User的空class。
public class User {  

}

2-2.產生對應json 的內容
假設已取得Server回傳的json string 如下
{  
"name":"Foxx",
"sex":"male",
"age":"99"
}

回到2-1建立的User class,點擊上方工具欄的Code,再點擊 Generate,選擇GsonFormat,在顯示的視窗中貼上2-2的內容。


點擊OK -> 再點擊OK,完成後User class 如下
public class User {  

private String name;
private String sex;
private String age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAge() {
return age;
}

public void setAge(String age) {
this.age = age;
}
}

從第3~29行即是GsonFormat產生的內容。

Orignal From: Gson 搭配 Gsonformat 基本使用紀錄

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Affiliate Network Reviews