在傳統的Android應用開發中,佈局文件一般只負責應用界面的佈局工做,若是須要實現頁面交互就須要調用setContentView()將Activity、fragment和XML佈局文件關聯起來。而後經過控件的id找到控件,接着在頁面中經過代碼對控件進行邏輯處理。在這種傳統的開發方式中,頁面承擔了大部分的工做量,大量的邏輯處理須要在Activity、Fragment中進行處理,所以頁面顯得臃腫不堪,維護起來也很困難,爲了減輕頁面的工做量,Google提出了DataBiiding(視圖綁定)。java
DataBinding的出現讓佈局文件承擔了本來屬於Activity、Fragment頁面的部分邏輯,使得Activity頁面和XML佈局之間的耦合度進一步下降。DataBinding主要有如下特色:android
事實上,DataBinding和MVVM架構是分不開的,DataBinding正是Google爲了可以更好的實現MVVM架構而實現的。api
視圖綁定功能可按模塊啓用,要在某個模塊中啓用視圖綁定,請將 viewBinding 元素添加到build.gradle 文件中,以下所示。網絡
android { ... viewBinding { enabled = true } }
DataBinding 是一個 support 包,添加完後上面的腳本後會發現工程的的External Libraries中多了四個aar包。分別是adapters、commen、runtime和viewbinding。架構
使用DataBinding時,若是但願在生成綁定類時忽略某個佈局文件,能夠將 tools:viewBindingIgnore="true"
屬性添加到相應佈局文件的根視圖中,以下所示。app
<LinearLayout ... tools:viewBindingIgnore="true" > ... </LinearLayout>
使用DataBinding的第一步,就是先改造XML文件。其實改造佈局文件也特別簡單,只須要在原來文件內容的基礎上,最外層改成<layout>
標籤便可,以下所示。ide
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> ... <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:textSize="24dp" android:text="HelloWord" /> ... </androidx.constraintlayout.widget.ConstraintLayout> </layout>
固然,咱們也能夠選中根佈局,按住【Alt + 回車鍵】而後選擇 【Convert to data binding layout】也能夠生成 DataBinding 須要的佈局規則。在佈局最外層加layout標籤後,從新編譯項目,DataBinding庫就會生成對應的Binding類,該類用來實現XML佈局文件與Model類的綁定,代碼以下。工具
public class ActivityMainBindingImpl extends ActivityMainBinding { @Nullable private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes; @Nullable private static final android.util.SparseIntArray sViewsWithIds; static { sIncludes = null; sViewsWithIds = null; } // views @NonNull private final androidx.constraintlayout.widget.ConstraintLayout mboundView0; // variables // values // listeners // Inverse Binding Event Handlers public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { this(bindingComponent, root, mapBindings(bindingComponent, root, 1, sIncludes, sViewsWithIds)); } private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { super(bindingComponent, root, 0 ); this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0]; this.mboundView0.setTag(null); setRootTag(root); // listeners invalidateAll(); } @Override public void invalidateAll() { synchronized(this) { mDirtyFlags = 0x1L; } requestRebind(); } @Override public boolean hasPendingBindings() { synchronized(this) { if (mDirtyFlags != 0) { return true; } } return false; } @Override public boolean setVariable(int variableId, @Nullable Object variable) { boolean variableSet = true; return variableSet; } @Override protected boolean onFieldChange(int localFieldId, Object object, int fieldId) { switch (localFieldId) { } return false; } @Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } // batch finished } // Listener Stub Implementations // callback impls // dirty flag private long mDirtyFlags = 0xffffffffffffffffL; /* flag mapping flag 0 (0x1L): null flag mapping end*/ //end }
生成的ActivityMainBindingImpl代碼位於app/build目錄下。生成Binding類的名字很特殊,它與XML佈局文件的名字有對應關係,具體的聯繫就是,以XML佈局文件爲準,去掉下劃線,全部單次以大駝峯的形式按順序拼接,最後再加上Binding。好比,個人XML佈局文件名是activity_main.xml,生成的Binding類名就是ActivityMainBinding。佈局
沒有使用DataBinding的時候,爲了將XML佈局文件與Activity進行綁定,須要調用Activity的setContentView()方法,或者是在Fragment中調用LayoutInflate的inflate()方法來進行佈局的綁定。若是使用了DataBinding以後,就須要使用DataBindingUtil類來進行視圖的綁定,以下所示。測試
public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding= DataBindingUtil.setContentView(this,R.layout.activity_main); } }
使用DataBindingUtil類的setContentView()方法對Activity進行綁定,其返回值就是工具生成的Binding類。若是是Fragment,則對應的綁定佈局的代碼以下。
private ActivityMainBinding binding; @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = ActivityMainBinding.inflate(inflater, container, false); View view = binding.getRoot(); return view; } @Override public void onDestroyView() { super.onDestroyView(); binding = null; }
通過前面的步驟後,咱們已經使用DataBinding將XML文件與UI組件綁定起來,若是要在XML文件中接受Model數據,就須要用到data標籤與variable標籤。
在XML文件的layout標籤下,建立data標籤,在data標籤中再建立variable標籤,variable標籤主要用到的就是name屬性和type屬性,相似於Java語言聲明變量時,須要爲該變量指定類型和名稱。新建一個名爲UserModel的實體類,代碼以下。
public class UserModel { private String firstName; private String lastName; public UserModel(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } }
而後在佈局的 data 標籤裏聲明要使用到的變量名、類的全路徑等信息,以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.xzh.jetpack.UserModel" /> </data> ... //省略其餘代碼 </layout>
若是 User 有多處用到,也能夠直接將之 import 進來,這樣就不用每次都指明整個包名路徑了,而 java.lang.* 包中的類會被自動導入,因此能夠直接使用。
<data> <import type="com.xzh.jetpack.UserModel" /> <variable name="user" type="UserModel" /> </data>
若是存在 import 的類名相同的狀況,可使用 alias 指定別名,以下所示。
<data> <import type="com.xzh.jetpack.UserModel" /> <import alias="TempUser" type="com.xzh.jetpack.uer.UserModel" /> <variable name="userInfo" type="User" /> <variable name="tempUserInfo" type="TempUser" /> </data>
在XML文件中聲明好variable屬性後,接下來就能夠在XML使用它了。使用variable屬性時須要使用到佈局表達式: @{ }
。能夠在佈局表達式@{ }中獲取傳入variable對象的值,以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="com.xzh.jetpack.UserModel" /> <variable name="user" type="UserModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:hint="Tv1" android:textSize="24dp" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:textSize="24dp" /> </LinearLayout> </layout>
而後,咱們在UI界面中使用Binding類爲每個variable標籤使用set方法傳遞數據,以下所示。
binding= DataBindingUtil.setContentView(this,R.layout.activity_main); UserModel user =new UserModel("zhang", "beijing"); binding.setUser(user);
通過上面的處理後,咱們已經給UserModel對象設置給了Binding類,因此這裏直接運行代碼就能夠看到效果了。
前面咱們介紹了DataBinding的一些基本用法,咱們能夠在佈局文件中對控件某些屬性進行賦值,使得Model類數據直接綁定在佈局中,並且Model屬性發生變化時,佈局文件中的內容能夠即時刷新。除了這些簡單的使用場景外,咱們還可使用DataBinding響應用戶事件。
咱們對佈局文件作一下修改,在裏面添加一個控件,而後在Activity中添加以下代碼。
binding.btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } });
除此以外,咱們還可使用另一種方式。佈局表達式不只能夠傳入對象的屬性,也能夠調用對象的方法。首先建立一個工具類,在類中定義響應事件的方法,以下所示。
public class ButtonClickListener { public void onClick(View view) { Log.d("ButtonClickListener","onClick..."); } }
而後在佈局文件中添加點擊事件的代碼,以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="btnHandler" type="com.xzh.jetpack.databinding.ButtonClickListener" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> ...//省略其餘代碼 <Button android:id="@+id/btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按鈕" android:textSize="24dp" android:onClick="@{btnHandler.onClick}"/> </LinearLayout> </layout>
在上面的代碼中,首先在data標籤中爲ButtonClickListener類聲明對象,在Button的onClick屬性中傳入佈局表達式便可。
在Android應用開發中,爲了可以讓佈局文件獲得複用,在編寫佈局的時候咱們常常會使用include標籤,相同結構與內容的佈局文件就能夠在多處使用。可是若是一個佈局文件中使用了DataBinding,同時也使用了include標籤,那麼如何使用nclude標籤引入的佈局文件中中的數據呢。
此時,咱們須要在同一級頁面的include標籤中,經過命名控件xmlns:app來引入佈局變量User,將數據對象傳遞給二級頁面,以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.xzh.jetpack.UserModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <include layout="@layout/layout_data_binding" app:persondata="@{user}" /> ... //省略其餘代碼 </LinearLayout> </layout>
佈局表達式中直接傳入頁面變量user,include標籤屬性值能夠任意取名,可是要注意的是,在二級頁面的variable標籤中的name屬性,必須與一級頁面中的include標籤屬性名一致,如layout_data_binding的代碼所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="userData" type="com.xzh.jetpack.UserModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{userData.firstName}" android:gravity="center" /> </LinearLayout> </layout>
使用DataBinding庫時,DataBinding會針對控件屬性生成對應的XXXBindingAdapter類,如TextViewBindingAdapter類,其對TextView的每一個可使用DataBinding的屬性都生成了對應的方法,並且每一個方法都使用了@BindingAdapter註解,註解中的參數就是對應View的屬性。
@RestrictTo(RestrictTo.Scope.LIBRARY) @SuppressWarnings({"WeakerAccess", "unused"}) @BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"), @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"), @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"), @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"), @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"), @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"), @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"), @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"), @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"), }) public class TextViewBindingAdapter { private static final String TAG = "TextViewBindingAdapters"; @SuppressWarnings("unused") public static final int INTEGER = 0x01; public static final int SIGNED = 0x03; public static final int DECIMAL = 0x05; @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { final CharSequence oldText = view.getText(); if (text == oldText || (text == null && oldText.length() == 0)) { return; } if (text instanceof Spanned) { if (text.equals(oldText)) { return; // No change in the spans, so don't set anything. } } else if (!haveContentsChanged(text, oldText)) { return; // No content changes, so don't set anything. } view.setText(text); } @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged") public static String getTextString(TextView view) { return view.getText().toString(); } @BindingAdapter({"android:autoText"}) public static void setAutoText(TextView view, boolean autoText) { KeyListener listener = view.getKeyListener(); TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE; int inputType = listener != null ? listener.getInputType() : 0; if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { capitalize = TextKeyListener.Capitalize.CHARACTERS; } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { capitalize = TextKeyListener.Capitalize.WORDS; } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { capitalize = TextKeyListener.Capitalize.SENTENCES; } view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize)); } }
BindingAdapter類中,全部的方法都是static方法,而且每一個方法都使用了@BindingAdapter註解,註解中聲明所操做的View屬性,當使用了DataBinding的佈局文件被渲染時,屬性所對應的static方法就會自動調用。
除了使用庫自動生成的BindingAdapter類以外,開發者也能夠自定義BindingAdapter類,供開發者來實現系統沒有提供的屬性綁定,或者是修改原有的屬性。
例如,有下面這樣一個需求:咱們但願能夠根據圖片地址動態的改變顯示圖片。若是使用BindingAdapter 如何實現呢?
此處,咱們加載圖片使用的是glide圖片庫,而且加載圖片須要訪問網路,因此請確保申請了網路權限。
<uses-permission android:name="android.permission.INTERNET"/>
接下來,咱們編寫一個處理圖片的自定義BindingAdapter類。而後定義一個靜態方法,主要用於添加 BindingAdapter 註解,註解值是 ImageView 控件自定義的屬性名,以下所示。
public class ImageBindingAdapter { @BindingAdapter({"url"}) public static void loadImage(ImageView view, String url) { if(!TextUtils.isEmpty(url)){ Glide.with(view) .load(url) .centerCrop() .placeholder(R.drawable.ic_launcher_background)//加載中顯示的圖片 .error(R.drawable.ic_launcher_foreground)// 錯誤後顯示的圖片 .into(view); } } }
能夠發現沒,loadImage()靜態方法的兩個參數,第一個參數必須是所操做的View類型,第二個參數是圖片的地址。當 ImageView 控件的 url 屬性值發生變化時,dataBinding 就會將 ImageView 實例以及新的 url 值傳遞給 loadImage() 方法,從而實現動態改變 ImageView的相關屬性。
而後,咱們在 XML 文件中關聯變量值,以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <ImageView android:layout_width="300dp" android:layout_height="200dp" app:url="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/> </LinearLayout> </layout>
須要說明的是,請注意佈局文件的最外層包含如下命名控件,這樣才能調用@BindingAdapter
標籤訂義的靜態方法。
xmlns:app="http://schemas.android.com/apk/res-auto"
須要說明的是,Activity和XML綁定的代碼必定要用DataBindingUtil,以下所示。
DataBindingUtil.setContentView(this,R.layout.activity_main);
通過上面的處理後,咱們就能夠很方便的使用 imageUrl 屬性來加載網絡圖片了,而且不用擔憂線程切換問題,DataBinding 庫會自動完成線程切換。運行上面的代碼,效果以下所示。
有時候,咱們須要自定義多個屬性,那如何處理呢?和一個參數同樣,咱們只須要使用BindingAdapter添加參數便可,以下所示。
public class ImageBindingAdapter { @BindingAdapter(value = {"url", "placeholder", "error"}) public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) { if(!TextUtils.isEmpty(url)){ RequestOptions options = new RequestOptions(); options.placeholder(placeholder); options.error(error); Glide.with(view.getContext()) .load(url) .apply(options) .into(view); } } }
而後在佈局中傳入屬性值便可,以下所示。
<ImageView android:layout_width="300dp" android:layout_height="200dp" android:layout_marginTop="10dp" app:url="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}" app:placeholder="@{@drawable/icon}" app:error="@{@drawable/error}"/>
在某些狀況下,咱們須要對設置屬性時類型進行轉化,類型轉化能夠藉助註解 @BindingConversion
來完成。例如,android:background
屬性接收的是一個 Drawable ,可是咱們在使用的時候須要給databinding 表達式中設置一個顏色值,此時就須要 @BindingConversion
。
首先,建立一個顏色轉化的類ColorConversion,用於將顏色值轉化爲Drawable,以下所示。
public class ColorConversion { @BindingConversion public static ColorDrawable colorToDrawable(int color){ return new ColorDrawable(color); } }
而後,建立一個佈局文件,添加以下代碼。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal"> <!--類型轉換--> <ImageView android:layout_width="100dp" android:layout_height="100dp" android:background="@{true ? @color/colorRed : @color/colorBlue}"/> </LinearLayout> </layout>
在佈局中使用 @BindingConversion
註解時要使用相同類型,不然會報錯。
DataBinding的自己是對View層狀態的一種觀察者模式的實現,經過讓View與ViewModel層可觀察的對象(好比LiveData)進行綁定,當ViewModel層數據發生變化,View層也會自動進行UI的更新,此種場景稱之爲單向綁定。
但實際開發中,單向綁定並不能足夠全部的需求。例若有下面的場景:若是佈局中有一個EditText,當用戶在輸入框中輸入內容時,咱們但願對應的Model類可以實時更新,這就須要雙向綁定,DataBinding一樣支持這樣的能力。
實現雙向綁定須要用到ObservableField類,它可以將普通的數據對象包裝成一個可觀察的數據對象,數據能夠是基本類型變量、集合,也能夠是自定義類型。爲了實現雙向綁定,咱們須要先定義一個繼承自BaseObservable的ViewModel類,並提供get和set方法,以下所示。
public class UserViewModel extends BaseObservable { private String name; private String address; private int age; @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } ... //省略其餘代碼 }
而後,咱們在XML佈局文件中使用DataBinding,以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="userModel" type="com.xzh.jetpack.UserViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <EditText android:id="@+id/et1" android:layout_width="match_parent" android:layout_height="50dp" android:text="@={userModel.name}" /> <Button android:id="@+id/btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="30dp" android:paddingRight="30dp" android:textSize="24dp" android:text="保存" /> <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24dp" android:text="@={userModel.name}" /> </LinearLayout> </layout>
須要注意的是,不一樣於單向綁定,以前的佈局表達式是@{}
表達式,雙向綁定使用的佈局表達式是@={}
,多了一個等號。
接下來,咱們在Activity中添加獲取用戶輸入的邏輯,以下所示。
public class MainActivity extends AppCompatActivity { private final String TAG = "MainActivity"; private ActivityMainBinding activityMainBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityMainBinding= DataBindingUtil.setContentView(this,R.layout.activity_main); activityMainBinding.btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String name=activityMainBinding.et1.getText().toString(); UserViewModel viewModel=new UserViewModel(); viewModel.setName(name); activityMainBinding.setUserModel(viewModel); } }); } }
通過上面的處理後,雙向綁定就基本完成了。能夠發現,雙向綁定與LiveData很是類似,都是將普通的數據對象封裝成了可觀察對象,理論上兩者是能夠互相替代的,但LiveData具備生命週期感知能力,而且須要調用observe()方法進行監聽,而雙向綁定中更推薦使用ObservableField,不須要使用observe()方法,維護起來相對簡單。
列表佈局在Android應用開發中是很是常見的場景,實現列表佈局須要使用RecyclerView控件,DataBinding支持在RecyclerViieew中實現數據綁定。
使用RcyclerView,就須要用到Adapter,在Adapter中實例化Item佈局,而後將List中的數據綁定到佈局中,而DataBinding就能夠幫助開發者實例化佈局並綁定數據。
首先,咱們編寫Adapter的item佈局,在item佈局中使用DataBinding將User數據進行綁定,item_user.xml的代碼以下所示。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.xzh.jetpack.UserModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.address}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.age}" /> </LinearLayout> </layout>
接下來,編寫Adapter類業務處理,UserAdapter的代碼以下所示。
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> { private List<UserModel> mDataList; public UserAdapter(List<UserModel> mDataList) { this.mDataList = mDataList; } public class ViewHolder extends RecyclerView.ViewHolder { ItemUserBinding binding; public ViewHolder(@NonNull ViewDataBinding binding) { super(binding.getRoot()); this.binding=(ItemUserBinding)binding; } public ItemUserBinding getBinding() { return binding; } } @NonNull @Override public UserAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { ItemUserBinding binding= DataBindingUtil.inflate((LayoutInflater.from(parent.getContext())), R.layout.item_user,parent,false); return new ViewHolder(binding); } @Override public void onBindViewHolder(@NonNull UserAdapter.ViewHolder holder, int position) { UserModel model=mDataList.get(position); holder.getBinding().setUser(model); } @Override public int getItemCount() { if (mDataList.size()>0){ return mDataList.size(); } return 0; } }
能夠發現,以前咱們都是須要在ViewHolder中進行findViewById對子控件進行實例化,因爲咱們使用了DataBinding,因此再也不須要這些操做,只須要傳入生成的Binding類便可,而後在super中調用getRoot()方法返回根View。
在RecyclerView中使用DataBinding就是如此簡單,當List中的item數據發生變化時,列表中的內容也會隨之更新。
而後,按照RecyclerView的基本使用方法,咱們在MainActivity添加一些測試數據,並將它和UserAdapter進行綁定,代碼以下。
public class MainActivity extends AppCompatActivity { private final String TAG = "MainActivity"; private ActivityMainBinding activityMainBinding; private List<UserModel> userModels; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); initData(); initRecyclerView(); } private void initData() { userModels = new ArrayList<UserModel>(); for (int i = 0; i < 10; i++) { UserModel userModel = new UserModel("zhangsan"+1, "beijing"+i, "age"+i); userModels.add(userModel); } } private void initRecyclerView() { LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); activityMainBinding.recycle.setLayoutManager(layoutManager); activityMainBinding.recycle.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); UserAdapter adapter = new UserAdapter(userModels); activityMainBinding.recycle.setAdapter(adapter); } }
在上面的代碼中,對RecyclerView設置LayoutManager和Adapter屬於對View的一些複雜操做,這些操做能夠經過自定義BindingAdapter的方式進行簡化。首先,定義一個新的屬性,將數據List直接經過DataBinding在佈局文件中綁定,而且將這些操做都封裝到BindindAdapter中,Activity中再也不須要設置LayoutManager和Adapter操做。
首先,定義BindingAdapter,以下所示。
public class UserBindingAdapter { @BindingAdapter("users") void setUsers(RecyclerView recyclerView, List<UserModel> users ) { LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext()); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(layoutManager); UserAdapter adapter = new UserAdapter(users); recyclerView.setAdapter(adapter); } }
在上面的代碼中,咱們聲明瞭一個新的屬性users,而後使用@BindingAdapter修飾靜態方法,而後在方法裏面對RecyclerView設置LayoutManager和Adapter。接下來,咱們只須要佈局中使用DataBinding便可。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="com.xzh.jetpack.UserModel" /> <import type="java.util.List" /> <variable name="users" type="List<UserModel>" /> </data> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycle" android:layout_width="match_parent" android:layout_height="match_parent" app:users="@{users}"/> </layout>
參考:
Android Jetpack架構組件(七)之WorkManager
Android Jetpack架構組件(六)之Room
Android Jetpack架構組件(五)之Navigation
Android Jetpack架構組件(四)之LiveData
Android Jetpack架構組件(三)之ViewModel
Android Jetpack架構組件(二)之Lifecycle
Android Jetpack架構組件(一)與AndroidX