Android從零開始搭建MVVM架構(1)————DataBinding

在真正接觸並使用MVVM架構的時候,整我的都很差了。由於我的以爲,MVVM相對於MVC、MVP學習難度比較大,設計的知識點不是一點半點。因此想慢慢記錄下本身的成長。若有錯誤但願指正。java


從零開始搭建MVVM架構系列文章(持續更新):
Android從零開始搭建MVVM架構(1)————DataBinding
Android從零開始搭建MVVM架構(2)————ViewModel
Android從零開始搭建MVVM架構(3)————LiveData
Android從零開始搭建MVVM架構(4)————Room(從入門到進階)
Android從零開始搭建MVVM架構(5)————Lifecycles
Android從零開始搭建MVVM架構(6)————使用玩Android API帶你搭建MVVM框架(初級篇)
Android從零開始搭建MVVM架構(7) ———— 使用玩Android API帶你搭建MVVM框架(終極篇)android


首先看一張圖,(這裏就是一些人口中所說的「AAC框架」)git

我說下個人理解:AAC(Android Architecture Components) :其實是android官方提供的一系列組件,用來實現MVVM架構的。 這裏提下 lifecycles:就是處理UI界面的生命週期,在26版本之後的Support庫中,AppCompatActivity和SupportActivity中都實現了LifecycleOwner,內部已經對UI界面的生命週期作了處理了。咱們能夠直接代碼點進去,以下github

好了,回到DataBinding。這是MVVM框架的第一步。DataBinding是studio自帶的。只須要在咱們app build.gradle的android標籤下加上:markdown

dataBinding {
        enabled = true
    }
複製代碼

1、初始DataBinding(建議使用studio3.5,好用)

DataBinding最厲害的功能是能夠將咱們的數據和view綁定。這句話體現不出來,那能夠說成,DataBinding能夠將數據和xml綁定。並且還支持雙向綁定:意思你改了bean裏的數據,他會自動改變view裏顯示的數據。你改了xml裏的數據,如editText裏的數據,他會自動改變bean裏的數據。
在android標籤加上後,來到咱們的xml佈局下,對着xml的第一行,按下Alt + Enter,選擇 「Convert to data binding layout」,就能夠生成DataBinding的佈局規則網絡

生成以下,我這裏改爲了RelativeLayout佈局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    </data>

    <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">

    </RelativeLayout>
</layout>
複製代碼


Activity裏須要綁定下佈局,框架自動會生成DataBinding類,類名是:xml名稱+Binding。架構

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        //寫上這句後,上面的代碼能夠註釋哦
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    }
複製代碼

此外還能夠自定義生成類名,這裏我就自定義了類名MyBindingapp

<data class="MyBinding">

    </data>
複製代碼

1.一、設置數據及點擊事件

在xml裏的<data>標籤里加上要設置的數據,我這裏有一個String,有一個OnClickListener:框架

  • <variable>標籤裏的 name至關於數據引用
  • type 是數據類型,也能夠理解爲包名.類名。經常使用數據類型,直接寫類型
  • 在設置值的時候的時候用@{value},value就是<variable>裏的數據引用
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable name="textStr" type="String" />
            
        <variable name="onClickListener" type="android.view.View.OnClickListener" />

    </data>

    <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" >

        <TextView android:id="@+id/txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{textStr}" android:onClick="@{onClickListener}" />

    </RelativeLayout>
</layout>

複製代碼


那麼在Activity設置數據和設置點擊事件(意思設置了variable標籤後DataBinding會自動生成get和set方法。studio3.5後只要xml寫上,IDE會自動生成,低版本可能須要Make Project下)。以下就實現了一個設置數據,和設置點擊事件。dom

經過如下代碼能夠看到,只要綁定下佈局,經過Binding對象,能夠作任何事。今後再也不使用findViewById,亦或是butterKnife。

public class BaseUseActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityBaseuseBinding baseuseBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_baseuse);
        baseuseBinding = DataBindingUtil.setContentView(this, R.layout.activity_baseuse);
        baseuseBinding.setTextStr("這裏就能設置數據");
        baseuseBinding.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        baseuseBinding.txt.setText("點擊設置的數據");
    }
}
複製代碼

這裏還能調用類裏的方法,且須要特別注意,在使用DataBinding的時候,包名必定是小寫,否則找不到包名,假設咱們這裏定義個類,而後調用類裏的方法。

public class OnClickUtil {

    public void onClickWithMe(View view) {
        Toast.makeText(view.getContext(), "調用類裏的方法", Toast.LENGTH_SHORT).show();
    }
}
複製代碼

其餘步驟都同樣,惟一不一樣的是,調用類裏方法的寫法不一樣。假設button點擊調用。用::表示調用,後面接的是方法名。

<Button ... android:onClick="@{onClickUtil::onClickWithMe}" />
複製代碼

1.二、<import>和別名alias的使用

這裏咱們先定義同名的2個類User。放在不一樣包裏。

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
複製代碼

以前咱們的<data>標籤就能夠用<import>。

<data>
        <import type="com.lihang.databindinglover.bean.User"/>

        <variable name="use_first" type="User" />
    </data>
複製代碼


<import>的用法是在同一個xml裏須要用到屢次User的時候,type類型只須要寫<import>的類名就能夠表明了,就不須要老是寫包名.類型。但這個時候也就出現2個同名不一樣包的類是須要用到alias別名,否則類名重複了。

<data>
        <import type="com.lihang.databindinglover.bean.User"/>

        <import alias="loverUser" type="com.lihang.databindinglover.User"/>

        <variable name="user_first" type="User" />

        <variable name="user_second" type="loverUser" />
    </data>
複製代碼

Activity裏的使用都是很是簡單的,若是有不明白,稍後放出連接。


這裏還有特殊功能,好比咱們再佈局預覽頁面。一般會使用 tools:text="中間的"來預覽佈局,這個時候能夠經過

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name,default = 預覽文字}" />
複製代碼

使用default的時候,即便是studio3.5裏,也不提示,不過不影響。還有一點,在DataBinding裏,已經處理了null。因此這個時候你在Activity裏給user設置爲null。也不會崩潰


2、DataBinding在Fragment和RecyclerView裏的使用

在Fragment的使用和Activity裏的使用同樣。獲取根目錄的方式以下。

//注意獲取根佈局是
View view = activityAlisBinding.getRoot();
複製代碼

這裏重點介紹下再recyclerView裏的用法。咱們之前是否是寫ViewHolder寫的煩了?用上了DataBinding後,這麼告訴你一個ViewHolder就能搞定一切須要的ViewHolder

先看下咱們的惟一的ViewHolder。首先提下,自動生成的Binding的父類都是ViewDataBinding。我是把ViewHolder單獨拉出來了。這樣你們都能用:

public class NewViewHolder extends RecyclerView.ViewHolder {
    public ViewDataBinding binding;

    public NewViewHolder(ViewDataBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
    }
}
複製代碼


在Adapter裏只須要職業

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        ItemNewOrderBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_new_order, viewGroup, false);
        return new NewViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        NewViewHolder newViewHolder = (NewViewHolder) viewHolder;
        //若是是多佈局,那麼對binding進行一個 instansof的一個判斷就好。這樣咱們的ViewHolder永遠只須要一個
        ItemNewOrderBinding binding = (ItemNewOrderBinding) newViewHolder.binding;
        binding.txtName.setText("這樣就能使用了!!");
    }
複製代碼

3、單向數據綁定

單向綁定能夠理解爲,改變了bean對象裏的數據,就會自動改變咱們xml的顯示。這裏涉及到3個類: BaseObservable、ObservableField、ObservableCollection。看這個名字就知道有點相似觀察者模式

3.一、BaseObservable

首先咱們定義個以Dog類

public class Dog extends BaseObservable {

    //若是是public修飾的,直接用@Bindable
    @Bindable
    public String name;
    //若是是private修飾的,則在get方法使用@Bindable
    private String color;


    public void setDataOnlyName(String name, String color) {
        this.name = name;
        this.color = color;
        //只刷name字段
        notifyPropertyChanged(com.lihang.databindinglover.BR.name);
    }

    public void setDataAll(String name, String color) {
        this.name = name;
        this.color = color;
        //刷新所有字段
        notifyChange();
    }
    ...//省略部分代碼
}
複製代碼

這裏我同事改變了name和color的顏色,說明

  • bean對象須要繼承 BaseObservable
  • @Bindable 標註用來表示哪一個字段須要單向綁定。public修飾的能夠直接用@Bindable綁定。private修飾的須要在get()方法上用@Bindable標註
  • notifyChange();刷新全部字段,notifyPropertyChanged(com.lihang.databindinglover.BR.name);刷新單個字段。注意這裏說的刷新全是被@Bindable綁定的。若是BR.name出不來。建議build下項目
  • 還有不明白的能夠在末尾連接demo看:單向數據綁定 -- BaseObservable.

繼承了BaseObservable的bean對象,還能夠監聽刷新了哪

dog.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == com.lihang.databindinglover.BR.name) {
                    Log.e("看看刷新了哪", "刷新了name");
                } else if (propertyId == com.lihang.databindinglover.BR._all) {
                    Log.e("看看刷新了哪", "所有所有");
                } else {
                    Log.e("看看刷新了哪", "未知錯誤~");
                }
            }
        });
複製代碼

3.二、ObservableField

其實這個ObservableField就是對BaseObservable的簡化,不用繼承,不用主動調刷新代碼。
這個時候咱們頂一個Human類

public class Human {
    //這裏必須是常量,ObservableField<參數類型>
    //其實寫上了下面一句,就是BaseObservable,set,get, @Bindable,刷新都封裝了。直接看構造方法
    public final ObservableField<String> name = new ObservableField<>();
    //其中也封裝了基本數據類型:ObservableInt等
    public final ObservableInt age = new ObservableInt();

    public Human(String name,int age){
        this.name.set(name);
        this.age.set(age);
    }

}
複製代碼

Activity和xml裏的操做和以前的同樣,改變數據,自動改變xml只須要:

//簡直太方便了吧
human.name.set("玉璣子");
human.age.set(15);
複製代碼

3.三、ObservableCollection

一看就是集合,這裏和咱們經常使用的 List Map同樣。只不過這裏的ObservableList、ObservableMap是封裝好的。當咱們改變集合裏的數據時。xml也會改變。惟一要注意的是,在xml裏引用這些集合的時候<類型>,這些符號,會影響xml格式因此要轉義。用&lt; 表明<;用&gt表明>(這些轉義符,一樣支持Mark Down);想了解更多可自行百度 DataBinding轉義符。

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable name="list" type="androidx.databinding.ObservableList&lt;String&gt;" />

        <variable name="map" type="androidx.databinding.ObservableMap&lt;String,String&gt;" />

        <variable name="index" type="int" />

        <variable name="key" type="String" />
    </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="@{list[index],default = 哈哈}" />

        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{map[key],default = 呵呵}" />

    </LinearLayout>
</layout>
複製代碼

這裏遇到一個坑,就是你的default = 「默認值」 這個默認值最好不是data裏的引用。否則會報錯哦。這裏咱們帶入index = 0 帶入,把key = name。代入,而後動態改變,集合裏這2個值:

@Override
    public void onClick(View v) {
        int randowInt = new Random().nextInt(100);
        switch (v.getId()){
            case R.id.btn_index:
                //改變list的第一項
                list.add(0,"list的值" + randowInt);
                break;
            case R.id.btn_key:
                map.put("name","map的值" + randowInt);
                break;
        }
    }
複製代碼


4、雙向數據綁定

意思就是你改變bean對象裏的值,他會主動改變xml的顯示,改變xml的裏的值,他會把bean對象裏的屬性改變了。 這裏咱們用1個TextView顯示數據;用1個EditTextView綁定bean對象,再用1個Button能夠動態查詢bean對象裏的屬性值

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable name="human" type="com.lihang.databindinglover.bean.Human" />
    </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="@{human.name}" />

        <EditText android:layout_width="match_parent" android:layout_height="60dp" android:layout_marginTop="20dp" android:text="@={human.name}" />

        <Button android:id="@+id/btn_search" android:layout_marginTop="60dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="動態查詢屬性" />
    </LinearLayout>
</layout>
複製代碼

bean對象綁定xml顯示:單向綁定是@{屬性值},雙向綁定則是@={屬性值},效果以下:

5、在include 和 viewStub中使用

5.1 在include中使用。

include的佈局以下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable name="user" type="com.lihang.databindinglover.bean.User" />

    </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="@{user.name}" />

    </LinearLayout>
</layout>
複製代碼


Activity裏引用include這樣:

<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.lihang.databindinglover.bean.User" />

    </data>

    <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">

        <include layout="@layout/include_item" app:user="@{user}" />

    </RelativeLayout>
</layout>
複製代碼

注意:app:user="@{user}"。第一個user是include裏name的引用。第二user是當前傳入的值。


5.二、viewStub中的使用

簡單介紹下viewStub:被viewStub包裹的。即便頁面顯示的時候,被包裹的佈局也不會加載,除非調用inflate。這樣算是對佈局卡頓的優化了。include則算是代碼裏的佈局優化。

直接放Activity佈局了。被包裹的佈局和上面的include同樣

<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.lihang.databindinglover.bean.User" />
    </data>

    <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">

        <ViewStub android:id="@+id/view_stub" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout="@layout/viewstub_layout" app:user="@{user}" />

    </RelativeLayout>
</layout>
複製代碼


activity裏:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewstub);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_viewstub);
        User user = new User("我愛學習", 18);
        binding.viewStub.getViewStub().inflate();
        binding.setUser(user);
    }
複製代碼

6、@BindingAdapter的使用。

這裏比較重要的用法是,當咱們的imageView須要加載網絡url時,假如用的是glide去加載,這個時候就須要使用@BindingAdapter。 這個須要一個輔助類:new一個輔助類後,xml裏就可使用了。有點像Dagger2

public class DataBindingHelper {
    //用@BindingAdapter標註,有點相似自定義屬性,後面是屬性名,方法體相似獲得屬性值後去作的事情。
    //第一個參數:是當前的控件類型,其實也能夠寫成View,可是要加載仍是要判斷是不是imageView
    //第二個參數:是網絡加載的url。
    @BindingAdapter("imageWithGlide")
    public static void loadImage(ImageView imageView, String url) {
        Glide.with(imageView).load(url)
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .transition(withCrossFade())
                .centerCrop()
                .into(imageView);
    }

    //@BindingAdapter還能修改系統屬性值,這是修改textView的屬性,意思只要使用DataBinding給textView設置setText值的,
    //都會加上後面這段 " - 我是經過方法加的"
    //我這裏先註釋掉了。否則整個項目的textView都會加上整個,若是要測試,能夠打開
    //@BindingAdapter("android:text")
    //public static void setText(TextView textView, String testStr) {
    // textView.setText(testStr + " - 我是經過方法加的");
    //}
}
複製代碼


咱們的xml就是專業:

<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable name="imageUrl" type="String" />

        <variable name="testStr" type="String" />

    </data>


    <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent">

        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/img" android:layout_centerHorizontal="true" android:layout_marginTop="15dp" android:text="@{testStr}" />

        <ImageView android:id="@+id/img" android:layout_width="100dp" android:layout_height="100dp" android:layout_centerInParent="true" app:imageWithGlide="@{imageUrl}" />

    </RelativeLayout>
</layout>
複製代碼

activity裏就是設置imageUrl和testStr的值。太簡單就不寫了。


7、DataBinding佈局裏支持的語法

支持的語法:

  • 算術 + - / * %
  • 字符串合併 +
  • 邏輯 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比較 == > < >= <=
  • Instanceof
  • Grouping ()
  • character, String, numeric, null
  • Cast
  • 方法調用
  • Field 訪問
  • Array 訪問 []
  • 三元 ?:

不支持的語法:

  • this
  • super
  • new
  • 顯示泛型調用

Github下載,你的點贊是我最大的動力

個人公衆號,剛開始玩

相關文章
相關標籤/搜索