DataBinding使用指南(一)DataBinding基本使用,雙向綁定,ListView RecycleView使用

簡介

隨着Android的發展,最初的MVC框架遠遠無法滿足廣大_Android猿_ 的需求,進而出現了MVP、MVVM等開發框架,Databing就是Google官方出品的支持MVVM開發的一個依賴庫。或許說依賴庫不準確,應該說是一整套開發工具和依賴庫的集合
今天我們不講開發框架的問題,我們只是來看一下 databing 的使用

簡單使用

  1. 在相關模塊下的buidl.gradle中設置對databing的支持
   		android {
   			......
   			//MVVM dataBinding 支持
   			dataBinding {
       			enabled = true
   			}
   			......
   		}
  1. gradle.properties中添加下面一句代碼開啓數據綁定編輯
    android.databinding.enableV2=true
    以上兩步完成後我們就可以開心的使用databing的方式進行編碼了。
  2. 代碼編寫
  • 定義 ViewModel類定義 ViewModel類
public class User extends BaseObservable {

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

    public User(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = 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 int getAge() {
        return age;
    }

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

這裏的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.caozy.demo1.bean.User" />
    </data>
    <!-- 視圖元素節點 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@{user.name}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@{user.sex}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@{String.valueOf(user.age)}" />

        <Button
            android:id="@+id/btn_1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="Click Me !" />

        <Button
            android:id="@+id/btn_2"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="btn2"
            />
    </LinearLayout>
</layout>

在這裏xml文件中的根標籤不再是一個容器,而是變成了<layout></layout>並且在頭部添加了<data></data>標籤,和java文件中的import有類似的作用。我們一般創建的xml文件的根標籤都是容器,這裏有一個快捷鍵可以使用,將光標放在根標籤處使用快捷鍵 Mac (⌥ + Enter) wimdows(Alt + Enter)你會看到如下提示在這裏插入圖片描述
選擇 Convert to data binding layout可以很快捷的將普通的xml佈局文件轉換成 dataBinding 需要的xml形式
data binding layout 有兩部分 數據元素(即<data></data>節點)和視圖元素

數據元素的導入方法

  1. <variable>標籤直接導入。
    <data>
        <variable
            name="user"
            type="com.caozy.demo1.bean.User" />
        <variable
            name="user2"
            type="com.caozy.demo1.samename.User"/>
    </data>

代碼中 name 是我們控件綁定數據時的實例引用,類似我們對java實例的應用。
type就是我們要導入的數據類型
2. 結合 <import> 標籤導入

    <data>
        <import type="com.caozy.demo1.bean.User" />
        <!-- 取了別名的 import -->
        <import type="com.caozy.demo1.samename.User"
            alias="user1"
            />
        <variable
            name="user"
            type="User" />
        <variable
            name="user1"
            type="user1"/>
    </data>

如上代碼中,如果在不同包中有同名的類,你可以在導入這個類時取一個別名,<variable> 中type用這個別名就可以了。

數據元素的綁定
佈局中使用@{}語法在屬性中綁定數據。
android:text="@{user.name}" 這裏就是將text屬性綁定爲user的name屬性

  • activity中代碼
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("張三", "男", 18);
        binding.setUser(user);
    }

這裏可能會有很多人奇怪ActivityMainBinding類的由來。這個類當你將xml轉換成data binding layout 的時候就自動生成了。默認情況下生成的Binding類名是根據佈局文件名稱生成的,大寫字母開頭,移除下劃線並大寫後面的字母最後加上“Binding”後綴。Binding類位於包com.caozy.demo1.databinding.ActivityMainBinding,當然這是在build文件夾下。也可以指定Binding類的名字,看代碼

    <data class="Main"></data><data class=".Main"></data><data class="com.caozhy.Main"></data>

這裏有幾種簡單的生成規則

  1. 佈局中設置ID的view會在Binding類中生成 public final 變量。生成規則爲View的ID名首字母小寫,移除下劃線並大寫後面的字母。例如 tv_name會生成tvName。
  2. 對於layout中使用的這些綁定 當我們沒有調用binding.setUser(user);的時候是有默認值的。引用類型爲null,int爲0,boolean爲false等

雙向綁定

上面只是簡單的顯示一些數據,一但數據發生變化就監測不到了。別急,下面我們要講的雙向綁定就是解決這個問題的。給個栗子:

從上面的例子我們看出兩點

  1. 頁面上的輸入可以改變數據的值
  2. 數據的改變可以實時的顯示在頁面上

要實現雙向綁定在以上使用的基礎上要做以下幾點改動

  • 實體類中 這裏有兩種方法
    • 實體類中的成員變量或者 get方法使用@Bindable註解 (如下代碼中name屬性)
    • 或者 使用ObservableField定義成員變量 (如下代碼中 content 屬性)
public class User extends BaseObservable {
    //使用 @Bindable 註解實現雙向綁定
    @Bindable
    private String name;
    private String sex;
    //使用 ObservableField 定義 成員變量實現雙向綁定
    private ObservableField<String> content = new ObservableField<>();

    public ObservableField<String> getContent() {
        return content;
    }

    public void setContent(ObservableField<String> content) {
        this.content = content;
    }

    //@Bindable //也可以在get方法上使用 註解
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        //更新頁面數據
        notifyPropertyChanged(BR.name);
    }

    public String getSex() {
        return sex;
    }

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

注意: 使用 ObservableField時有一點需要注意,定義的屬性一定要調用new ObservableField<>()初始化
這裏對於 基本數據類型list map 等都有對於的Observable類可供使用

  • xml中
    @{} 改成@={}即可
    這樣就可以實現上面的例子的效果
    有幾點想說:
  1. 如果我們只使用了@Bindable 註解而沒有在set方法中調用notifyPropertyChanged(BR.name);那麼上例中editText內容的改變會引起user實例name值的改變,但是不會更新頁面中textView的值。
  2. 我們不僅可以在user實體類的set方法中調用notifyPropertyChanged(BR.name)也可以在任何我們需要的地方用user類的對象調用這一方法
    再給個栗子:

    這裏就是將user類中的setName 方法中的notifyPropertyChanged(BR.name) 代碼去掉,Activity中添加button的點擊代碼
        binding.btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //更新頁面中user的name屬性
                user.notifyPropertyChanged(BR.name);
            }
        });
  1. 當我們沒有用雙向綁定的時,如果user的數據發生了變化,我們也可以再次調用binding.setUser(user);方法更新頁面數據,每次調用 binding.setUser(user);都會進行頁面數據的更新

ListView、RecycleView中的使用

. ListView

ListView 中數據的簡單展示

activity的代碼跟你平時是一樣的。xml的代碼還是那個樣子,只是 item的xml改成dataBinding layout 就可以了。
我們主要看一下Adapter的代碼

public class ListAdapter extends BaseAdapter {
    
    private Context context;
    private List<User> users;

    public ListAdapter(Context context, List<User> users) {
        this.context = context;
        this.users = users;
    }

    @Override
    public int getCount() {
        return users.size();
    }

    @Override
    public User getItem(int position) {
        return users.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemListBinding binding;
        if (convertView == null) {
            //這裏的最後一個參數必須爲 false
            binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_list, parent, false);
            convertView = binding.getRoot();
        } else {
            binding = DataBindingUtil.getBinding(convertView);
        }
        binding.setUser(getItem(position));
//        binding.setVariable(BR.user, getItem(position));
        return convertView;
    }
}

Adapter的代碼暫時就這些了。
有的朋友會說:”你這個爲什麼沒有用 ViewHolder呀?”。其實,dataBinding 已經對這方面做了處理。 不信你跟蹤DataBindingUtil.getBinding(convertView);這行代碼進去。跟蹤兩步,你最終會發現這麼一句代碼return (ViewDataBinding) v.getTag(R.id.dataBinding);是不是這就很熟悉了。

數據源改變後數據更新方式

經過上面第一步,你一經展示出了list的數據。有些人可能會發現有問題,不要着急,因爲確實有問題,即使沒有問題經過下面的操作也會發現問題。
修改數據源,並且不去調用我們通常調用的ListAdapter.notifyDataSetChanged();此時點擊listview中的某一項,會發現程序閃退。對於這個問題我們並不陌生。報錯信息爲java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131165264, class android.widget.ListView) with Adapter(class com.caozy.listview1demo.ListAdapter)] 這裏有兩種方式解決這個問題。

  1. 和之前一樣當數據源改變時主動調用 ListAdapter.notifyDataSetChanged()來刷新界面。
  2. 將數據源中的List替換成ObservableList。這個接口繼承 List接口。所以用法和List一樣,只是多了一個數據變化的監聽OnListChangedCallback;只是一個接口所以DataBinding也提供了可供實例化的ObservableArrayList,這個類繼承ArrayList。我們倆看下adapter的代碼,
public class ListAdapter extends BaseAdapter {

    private Context context;
    private ObservableList<User> users;

    public ListAdapter(Context context, ObservableList<User> users) {
        this.context = context;
        this.users = users;
        //設置數據變化監聽
        users.addOnListChangedCallback(new ListChangeListener());
    }

    @Override
    public int getCount() {
        return users.size();
    }

    @Override
    public User getItem(int position) {
        return users.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemListBinding binding;
        if (convertView == null) {
            //這裏的最後一個參數必須爲 false
            binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_list, parent, false);
            convertView = binding.getRoot();
        } else {
            binding = DataBindingUtil.getBinding(convertView);
        }
        binding.setUser(getItem(position));
//        binding.setVariable(BR.user, getItem(position));
        return convertView;
    }

    /**
     * 數據變化監聽
     */
    private class ListChangeListener extends ObservableList.OnListChangedCallback {

        @Override
        public void onChanged(ObservableList sender) {
            ListAdapter.this.notifyDataSetChanged();
        }

        @Override
        public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
            ListAdapter.this.notifyDataSetChanged();
        }

        @Override
        public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
            ListAdapter.this.notifyDataSetChanged();
        }

        @Override
        public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
            ListAdapter.this.notifyDataSetChanged();
        }

        @Override
        public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
            ListAdapter.this.notifyDataSetChanged();
        }
    }
}

我們看到代碼並沒有多大的變化,只是將list替換成ObservableList並對其添加數據監聽。這樣就無需在數據變化時調用ListAdapter.notifyDataSetChanged()

. RecycleView

其他都和平時的使用是一樣的。我們就來看一下adapter中代碼

public class RecycleViewAdapter extends RecyclerView.Adapter<RecycleViewAdapter.CzyViewHolder> {

    ObservableList<User> mList;

    public RecycleViewAdapter(ObservableList<User> mList) {
        this.mList = mList;
        mList.addOnListChangedCallback(new CzyOnListChangeCallback());
    }

    @NonNull
    @Override
    public CzyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        //這裏的第四個參數必須爲 false 這在Google
        ItemRecycleViewBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_recycle_view, viewGroup, false);
        return new CzyViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(@NonNull CzyViewHolder czyViewHolder, int i) {
        ItemRecycleViewBinding binding = DataBindingUtil.getBinding(czyViewHolder.itemView);
        binding.setUser(mList.get(i));
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    class CzyViewHolder extends RecyclerView.ViewHolder {

        public CzyViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    //數據變化監聽
    class CzyOnListChangeCallback extends ObservableList.OnListChangedCallback {

        @Override
        public void onChanged(ObservableList sender) {}

        @Override
        public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
            RecycleViewAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
        }

        @Override
        public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
            RecycleViewAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
        }

        @Override
        public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
            RecycleViewAdapter.this.notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
            RecycleViewAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
        }
    }
}

這篇文章我們主要講了一下dataBinding對控件和list視圖等進行數據綁定。我們發現僅僅簡單的綁定了文字,對一些複雜的數據處理我們將在下一章進行講解。
如:網絡圖片的加載等。

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