MVVM的基本使用

MVVM的基本概念

項(xiàng)目源碼地址: https://github.com/corffen/MVVMDemo

1.什么是MVVM?

它是model-view-viewmodle的縮寫

為何要用MVVM?

需求不斷地提出,應(yīng)用一天比一天復(fù)雜,fragment和activity開始膨脹,逐漸變得難以理解和擴(kuò)展,這個(gè)時(shí)候控制器層需要做功能拆分.
MVVM能能夠很好的把控制器里的臃腫代碼放入到布局文件里,很容易地看出哪些是動(dòng)態(tài)界面,同時(shí)抽出部分動(dòng)態(tài)控制器代碼放入viewModel中.

MVVM的基本使用

1.在build.gradle文件中添加

`

     dataBinding {
        enabled = true
    }

`

2.在布局文件中

將根目錄結(jié)構(gòu)換成layout

如:
`

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

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:gravity="center"
        android:text="基本使用"
        android:id="@+id/tv_title"
        android:layout_gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>
</layout>

`

3.在Activity或者Fragment中初始化DataBinding

`

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ActivityBasicBinding activityBasicBinding = DataBindingUtil.setContentView(this, R.layout.activity_basic);
    activityBasicBinding.tvTitle.setText("我不需要findviewbyid");
}

`
運(yùn)行效果:

[圖片上傳失敗...(image-a9e259-1524712938506)]

可以看到我們并沒有初始化TextView,直接使用DataBinding的打點(diǎn)調(diào)用控件就行了.

上面的ActivityBasicBinding這個(gè)東西其實(shí)是根據(jù)我們的布局文件名字自動(dòng)生成的.

這樣我們不需要手動(dòng)的去寫一大堆的findviewbyid了,當(dāng)然它的功能遠(yuǎn)不止這些.

我們想要的效果是view可以跟數(shù)據(jù)綁定起來,當(dāng)數(shù)據(jù)變化的時(shí)候,view會(huì)自己刷新UI

4.view綁定數(shù)據(jù)

假設(shè)我們需要的是TextView顯示的內(nèi)容是一個(gè)bean的屬性,比如User類中的一個(gè)name

那么可以向下面這樣去寫

首先在布局文件中添加
data標(biāo)簽

`

<data>
    <variable
        name="user"
        type="com.corffen.mvvmdemo.User"/>
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_view_bind_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:text="@{user.name}"/>

`

然后使用@{}語法糖去設(shè)置屬性

最后在Activity中將我們的bean類設(shè)置給DataBinding

`

final ActivityViewBindDataBinding activityViewBindDataBinding = DataBindingUtil.setContentView(this, R.layout
            .activity_view_bind_data);
    mUser = new User("綁定數(shù)據(jù)啊");
    activityViewBindDataBinding.setUser(mUser);

`
運(yùn)行效果如圖

image

我們并沒有調(diào)用textview的setText函數(shù),就可以直接顯示數(shù)據(jù)了.

現(xiàn)在我們點(diǎn)擊"改變數(shù)據(jù)按鈕",更改bean的屬性
添加如下:
`

  activityViewBindDataBinding.btnChangeData.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            count++;
            mUser.setName("我改變了數(shù)據(jù)" + count);
        }
    });

`

發(fā)現(xiàn)點(diǎn)擊按鈕之后,textview并沒有更改數(shù)據(jù).
這是因?yàn)槲覀冊(cè)邳c(diǎn)擊按鈕時(shí),數(shù)據(jù)bean類,已經(jīng)改變了,但是view并不知道,所以
我們?cè)邳c(diǎn)擊事件里再添加一行代碼
`

mUser.setName("我改變了數(shù)據(jù)" + count);
activityViewBindDataBinding.setUser(mUser);

`

再次運(yùn)行如圖:

[圖片上傳失敗...(image-14fdc3-1524712938506)]

也就是說我們?cè)邳c(diǎn)擊事件中,重寫給DataBinding設(shè)置了數(shù)據(jù)bean類,當(dāng)數(shù)據(jù)變化時(shí),需要通知view,view就可以正確的顯示我們想要的數(shù)據(jù)了.

那么這個(gè)setUser方法到底干什么了.點(diǎn)擊源碼進(jìn)去可以看到
`

public void setUser(@Nullable com.corffen.mvvmdemo.User User) {
    this.mUser = User;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.user);
    super.requestRebind();
}

`

調(diào)用了一個(gè)
notifyPropertyChanged(BR.user);這樣的方法去刷新數(shù)據(jù).那我們總不能再數(shù)據(jù)每次改變的時(shí)候,都去調(diào)用一次 activityViewBindDataBinding.setUser(mUser);這樣的方法,這也太low了.

BaseObservable的使用

首先讓我們的bean類繼承自BaseObservable,然后在get方法上添加一個(gè)@Bindable

然后在set方法中調(diào)用notifyChange(); 修改如下:
`

public class User2 extends BaseObservable {
private String name;

public User2(String name) {
    this.name = name;
}

@Bindable
public String getName() {
    return name;
}

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

`

布局文件可以不用動(dòng),然后我們把點(diǎn)擊事件中的DataBinding.setuser方法屏蔽掉,運(yùn)行結(jié)果:

[圖片上傳失敗...(image-228b6e-1524712938506)]

實(shí)現(xiàn)了與上面的一樣的效果.

點(diǎn)擊事件的實(shí)現(xiàn)

首先在布局文件中添加
`

  <Button
        android:id="@+id/btn_change_data3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:onClick="@{() ->user3.clickMe()}"
        android:text="@{user3.btnContent}"/>

`

上面text使用了User中btnContent屬性,然后還多了一個(gè)onClick
這是什么鬼?
這是給Button設(shè)置了一個(gè)點(diǎn)擊事件,具體的實(shí)現(xiàn)邏輯在User3中,如下:
`

 public void clickMe() {
    count++;
    setBtnContent("點(diǎn)我啊" + count);
}

然后在Activity中給DataBinding設(shè)置Data:

 @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityClickBinding activityClickBinding = DataBindingUtil.setContentView(this, R.layout.activity_click);
    activityClickBinding.setUser3(new User3("我是點(diǎn)擊事件", "點(diǎn)我啊"));
}

`
運(yùn)行效果:

image

可以看到Activity中的代碼基本不用怎么寫了,看起來很清爽,因?yàn)闃I(yè)務(wù)邏輯交給布局和model層去處理了,但是有一個(gè)問題,就是我們?cè)趍odel層做了邏輯處理,因?yàn)関iew和data交互的時(shí)候,data不可避免的要關(guān)心顯示問題,這就違背了單一原則,為了解決這個(gè)問題,我們想起了一句名言
“計(jì)算機(jī)科學(xué)領(lǐng)域的任何問題都可以通過增加一個(gè)間接的中間層來解決”

ViewModel

我們希望的是view作為界面的顯示,而bean類只作為基本的數(shù)據(jù),不去參與具體的邏輯處理.
如下代碼:
`

public class User4 {

private String btnContent;

public String getBtnContent() {
    return btnContent;
}

public void setBtnContent(String btnContent) {
    this.btnContent = btnContent;
}

public User4(String btnContent) {
    this.btnContent = btnContent;
}

}
`

所以我們還需要?jiǎng)?chuàng)建一個(gè)中間層來處理相應(yīng)的業(yè)務(wù)邏輯.
`

public class ClickViewModel extends BaseObservable {

private User4 mUser4;
private int count;

public ClickViewModel(User4 user4) {
    mUser4 = user4;
    count = 0;
}

@Bindable
public String getBtnContent() {
    return mUser4.getBtnContent();
}

public void setBtnContent(String content) {
    mUser4.setBtnContent(content);
    notifyChange();
}

public void clickMe() {
    count++;
    setBtnContent("點(diǎn)我啊" + count);
}
}

`

viewModel持有User4類,因?yàn)槲覀冃枰跀?shù)據(jù)變化的時(shí)候通知界面去刷新UI,所以我們的viewModel繼承BaseObservable,并給需要的BtnContent設(shè)置了@Bindable

然后看一下布局
`

    <data>
    <variable
        name="clickViewModel"
        type="com.corffen.mvvmdemo.ClickViewModel"/>
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:text="我是ClickViewModel界面"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:onClick="@{() ->clickViewModel.clickMe()}"
        android:text="@{clickViewModel.btnContent}"/>
</LinearLayout>
</layout>

注意到android:text="@{clickViewModel.btnContent}",而我們的viewModel并沒有提供btnContent這個(gè)屬性,實(shí)際上他是調(diào)用了getBtnContent()也就是說他是方法的簡(jiǎn)寫.另外值得一提的是我們?cè)贏ctivity中給DataBinding設(shè)置viewModel時(shí),使用的方法 如下:

   activityClickWithViewModelBinding.setClickViewModel(new ClickViewModel(new User4("點(diǎn)我啊!")));
    activityClickWithViewModelBinding.executePendingBindings();

`
這里的setClickViewModel其實(shí)是布局文件中data標(biāo)簽下的name屬性來的.
在數(shù)據(jù)變化時(shí),調(diào)用notifyChange,在讀取數(shù)據(jù)的方法上加上@Bindable注解.

多個(gè)property的ViewModel

在上一個(gè)例子當(dāng)中,我們要的數(shù)據(jù)是User4中的一個(gè)屬性,我們做的地方首先是viewModel繼承了BaseObservable,然后提供了get(需要@Bindable注解)和set(需要notifyChange)方法,這樣看起來好像有些麻煩,而且有時(shí)候我們自定義的ViewModel類可能繼承了別的類,比如我們?cè)诜庋b的時(shí)候,ViewModel可能已經(jīng)繼承了BaseViewModel,這樣我們就不能再繼承BaseObservable了,這樣數(shù)據(jù)變化了,UI便不能更新了.
所以系統(tǒng)提供了Observable系列的封裝類,供我們?nèi)ナ褂?

比如我們有多個(gè)屬性
`

   <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:onClick="@{() ->multiPropertyViewModel.click1()}"
        android:text="@{multiPropertyViewModel.content1}"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:onClick="@{() ->multiPropertyViewModel.click2()}"
        android:text="@{multiPropertyViewModel.content2}"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:onClick="@{() ->multiPropertyViewModel.click3()}"
        android:text="@{multiPropertyViewModel.content3}"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:onClick="@{() ->multiPropertyViewModel.click4()}"
        android:text="@{multiPropertyViewModel.content4 }"/>

我們的viewModel類可以這樣去寫

public class MultiPropertyViewModel {

private int count1 = 0, count2 = 0, count3 = 0, count4 = 0;
private ObservableField<String> mContent1 = new ObservableField<>("第一個(gè)按鈕");
private ObservableField<String> mContent2 = new ObservableField<>("第二個(gè)按鈕");
private ObservableField<String> mContent3 = new ObservableField<>("第三個(gè)按鈕");
private ObservableField<String> mContent4 = new ObservableField<>("第四個(gè)按鈕");

public MultiPropertyViewModel() {
}

public ObservableField<String> getContent1() {
    return mContent1;
}

public ObservableField<String> getContent2() {
    return mContent2;
}

public ObservableField<String> getContent3() {
    return mContent3;
}

public ObservableField<String> getContent4() {
    return mContent4;
}

public void click1() {
    count1++;
    mContent1.set("第一個(gè)按鈕點(diǎn)擊了" + count1);
}

public void click2() {
    count2++;
    mContent2.set("第二個(gè)按鈕點(diǎn)擊了" + count2);
}

public void click3() {
    count3++;
    mContent3.set("第三個(gè)按鈕點(diǎn)擊了" + count3);
}

public void click4() {
    count4++;
    mContent4.set("第四個(gè)按鈕點(diǎn)擊了" + count4);
}

}

`
不需要繼承自BaseObservable,然后定義4個(gè)ObservableField<String>,提供get方法
這里注意一個(gè)問題是,get方法一定要返回ObservableField<String>而不是String類的,否則看不到數(shù)據(jù)的更新,然后點(diǎn)擊按鈕的時(shí)候,我們更改了數(shù)據(jù),就調(diào)用
ObservableField<String>的set方法,運(yùn)行效果如下圖:

image

BindingAdapter的使用

經(jīng)常有一個(gè)這樣的場(chǎng)景,比如我有一個(gè)TextView,用來顯示一段文字,每當(dāng)數(shù)據(jù)變化的時(shí)候,我都需要調(diào)用此控件的setText方法,我有一個(gè)ImageView,在不同的狀態(tài)時(shí),需要顯示不同的圖片,就去調(diào)用類似setDrawable這樣的方法,而對(duì)于加載網(wǎng)絡(luò)圖片,只需要知道url就行了.然后使用開源框架,去加載圖片.
對(duì)于這些常見的場(chǎng)景,我們能不能只改變數(shù)據(jù),讓控件自動(dòng)去正確的顯示我們需要的數(shù)據(jù).

我們先來看一下下面的代碼
@BindingAdapter("imageUrl")
public static void setImageUrl(ImageView imageView, String url) {
Context context = imageView.getContext();
Glide.with(context).load(url).
into(imageView);
}
這是一個(gè)使用Glide加載圖片的方法,方法上面有一個(gè)注解BindingAdapter,還有一個(gè)參數(shù)imageUrl.
這個(gè)參數(shù)其實(shí)是一個(gè)屬性的意思,就是說我們給ImageView設(shè)置了一個(gè)自定義個(gè)屬性imageURL,當(dāng)我們給ImageView設(shè)置這個(gè)屬性,并給其值設(shè)置一個(gè)URL,那么就會(huì)調(diào)用這個(gè)方法去加載圖片.
在布局文件中我們就可以使用

    <ImageView
        android:id="@+id/iv_net"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:imageUrl="@{viewModel.imageUrl}" />

然后我們只需要關(guān)注viewModel.imageUrl這個(gè)值的變化就可以更改ImageView顯示的圖片了.上面的app:imageUrl 可以理解為,我們自定義了一個(gè)控件ImageView,然后寫了一個(gè)imageUrl的自定義屬性,它的值是String類型的,然后給這個(gè)屬性設(shè)置值時(shí),就去執(zhí)行相應(yīng)的@BindingAdapter的方法了.

其實(shí)我們?cè)谑褂肨extView時(shí),會(huì)見到這樣的一個(gè)寫法

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{viewModel.textContent}" />

當(dāng)我們的viewModel.textContent值變化時(shí),就會(huì)顯示對(duì)應(yīng)的text.假設(shè)我們有一個(gè)需求就是讓我們的TextView每次都顯示大寫的內(nèi)容.改怎么做呢?

這里的text,其實(shí)是系統(tǒng)提供給我們的一個(gè)BindingAdapter寫法.我們可以仿照系統(tǒng)提供的方法,然后自定義一個(gè)@BindingAdapter方法

  @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.
        }
        //下面這句代碼钉答,就是我們加進(jìn)去的
        CharSequence upperText = ((String) text).toUpperCase();
        view.setText(upperText);
    }

我們?cè)谠O(shè)置內(nèi)容時(shí),它的值就會(huì)變成大寫的了.運(yùn)行效果如圖:

源碼在GitHub中可以查看

RecyclerView的使用

Rv是使用頻率非常高的一個(gè)控件了,我們?cè)谑褂盟鼤r(shí),無非是當(dāng)數(shù)據(jù)源變化時(shí)寫Adapter去適配其顯示的方式.對(duì)Rv來說也就是說,給我們一個(gè)數(shù)據(jù)源,我就知道如何去顯示自己的內(nèi)容了.
所以利用上一節(jié)的內(nèi)容,可以寫以下的一個(gè)方法.

  @BindingAdapter("adapter")
    public static void setAdapterForRv(RecyclerView rv, List<String> datas) {
        RvAdatper adapter = (RvAdatper) rv.getAdapter();
        if (adapter != null) {
            adapter.clearItems();
            adapter.addItems(datas);
        }
}

這里說一下這個(gè)方法,可以寫很多的重載方法,當(dāng)給不同的數(shù)據(jù)源時(shí),Rv去尋找對(duì)應(yīng)的方法.比如我還有一個(gè)以下的方法

  @BindingAdapter("adapter")
    public static void setAdapterForRv(RecyclerView rv, List<Bean> datas) {
        RvAdatper adapter = (RvAdatper) rv.getAdapter();
        if (adapter != null) {
            adapter.clearItems();
            adapter.addItems(datas);
        }
    }

上面給adapter提供的兩個(gè)方法如下:

public void addItems(List<String> datas) {
    this.mDatas.addAll(datas);
    notifyDataSetChanged();
}

public void clearItems() {
    mDatas.clear();
}

對(duì)了這種@BindingAdapter的方法一定需要是靜態(tài)的,而且它放在任何類都可以,所以為了統(tǒng)一,我們經(jīng)常會(huì)將這樣的方法,統(tǒng)一放在一個(gè)類里.比如BindingUtils類.
寫好上面的方法后,在布局中就可以像下面這樣寫

  <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:adapter="@{viewModel.mStringObservableArrayList}"
            tools:listitem="@layout/item_string_rv">

        </android.support.v7.widget.RecyclerView>

上面的adapter就是我們給Rv自定義的屬性,它的值是一個(gè)List<String>,它有點(diǎn)特殊,因?yàn)槲覀冃枰^察數(shù)據(jù)變化時(shí)去刷新UI,所以我們用了一個(gè)ObservableArrayList,這個(gè)跟上面講的ObservableField系列沒啥不同,它只是用來存放List類型的數(shù)據(jù)而已.

 tools:listitem="@layout/item_string_rv" 這行代碼是用來告訴IDE,我們?cè)赼s中預(yù)覽到我們的item而已.

所以在寫好常規(guī)的adapter中后,我們只需要關(guān)注數(shù)據(jù)源的變化就可以了.

public class RvViewModel extends BaseObservable {

    public final ObservableArrayList<String> mStringObservableArrayList = new
            ObservableArrayList<>();

    private int count = 0;

    public RvViewModel() {

    }

    public void setData() {
        for (int i = 0; i < 9; i++) {
            mStringObservableArrayList.add("我是item" + i);
        }
    }

    public void addData() {
        count++;
        mStringObservableArrayList.add("我是添加的數(shù)據(jù)" + count);
    }

    public void clearData() {
        mStringObservableArrayList.clear();
    }

    public void clearOneData() {
        if (mStringObservableArrayList.size() > 0) {
            mStringObservableArrayList.remove(mStringObservableArrayList.size() - 1);
        }
    }
}

在給Activity中設(shè)置Rv的使用如下啊:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityRvBinding = DataBindingUtil.setContentView(this, R.layout
                .activity_rv);
        mRvViewModel = new RvViewModel();
        activityRvBinding.setViewModel(mRvViewModel);
        //給數(shù)據(jù)源初始化數(shù)據(jù)
        mRvViewModel.setData();
        setAdapter();
    }

    private void setAdapter() {
        LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL,
                false);
   //注意這里并沒有在構(gòu)造中設(shè)置數(shù)據(jù)源
        mRvAdatper = new RvAdatper(new ArrayList<String>());
        activityRvBinding.rv.setLayoutManager(manager);
        activityRvBinding.rv.setAdapter(mRvAdatper);
}

然后運(yùn)行效果如圖:

LiveData和MultiLiveData

http://www.reibang.com/p/13a855ceaf2b
這里有一篇鏈接,講的挺好的.

LiveData 定義

假設(shè)有這樣的一個(gè)場(chǎng)景,我在Activity中的onCreate方法中執(zhí)行了一段異步加載數(shù)據(jù)的邏輯,
當(dāng)加載完數(shù)據(jù)之后,需要刷新我的界面.可是我在加載數(shù)據(jù)尚未完成的時(shí)候,屏幕旋轉(zhuǎn),導(dǎo)致我們重新加載數(shù)據(jù),異步線程加載的數(shù)據(jù)要在一個(gè)已經(jīng)銷毀的控件上顯示會(huì)導(dǎo)致錯(cuò)誤.這樣的解決方式,我們有多種,而我們要講的主題是LiveData.

簡(jiǎn)單地說唇牧,LiveData是一個(gè)數(shù)據(jù)持有類橱健。它具有以下特點(diǎn):

  • 數(shù)據(jù)可以被觀察者訂閱;

  • 能夠感知組件(Fragment、Activity埠况、Service)的生命周期炕泳;

  • 只有在組件出于激活狀態(tài)(STARTED、RESUMED)才會(huì)通知觀察者有數(shù)據(jù)更新忙干;

LiveData的優(yōu)點(diǎn)

從LiveData具有的特點(diǎn)器予,我們就能聯(lián)想到它能夠解決我們遇到的什么問題。LiveData具有以下優(yōu)點(diǎn):

  • 能夠保證數(shù)據(jù)和UI統(tǒng)一
    這個(gè)和LiveData采用了觀察者模式有關(guān)捐迫,LiveData是被觀察者乾翔,當(dāng)數(shù)據(jù)有變化時(shí)會(huì)通知觀察者(UI)。

  • 減少內(nèi)存泄漏
    這是因?yàn)長(zhǎng)iveData能夠感知到組件的生命周期施戴,當(dāng)組件處于DESTROYED狀態(tài)時(shí)反浓,觀察者對(duì)象會(huì)被清除掉。

  • 當(dāng)Activity停止時(shí)不會(huì)引起崩潰
    這是因?yàn)榻M件處于非激活狀態(tài)時(shí)赞哗,不會(huì)收到LiveData中數(shù)據(jù)變化的通知雷则。

  • 不需要額外的手動(dòng)處理來響應(yīng)生命周期的變化
    這一點(diǎn)同樣是因?yàn)長(zhǎng)iveData能夠感知組件的生命周期,所以就完全不需要在代碼中告訴LiveData組件的生命周期狀態(tài)肪笋。

  • 組件和數(shù)據(jù)相關(guān)的內(nèi)容能實(shí)時(shí)更新
    組件在前臺(tái)的時(shí)候能夠?qū)崟r(shí)收到數(shù)據(jù)改變的通知月劈,這是可以理解的度迂。當(dāng)組件從后臺(tái)到前臺(tái)來時(shí),LiveData能夠?qū)⒆钚碌臄?shù)據(jù)通知組件猜揪,這兩點(diǎn)就保證了組件中和數(shù)據(jù)相關(guān)的內(nèi)容能夠?qū)崟r(shí)更新惭墓。

  • 針對(duì)configuration change時(shí),不需要額外的處理來保存數(shù)據(jù)
    我們知道而姐,當(dāng)你把數(shù)據(jù)存儲(chǔ)在組件中時(shí)腊凶,當(dāng)configuration change(比如語言、屏幕方向變化)時(shí)毅人,組件會(huì)被recreate吭狡,然而系統(tǒng)并不能保證你的數(shù)據(jù)能夠被恢復(fù)的。當(dāng)我們采用LiveData保存數(shù)據(jù)時(shí)丈莺,因?yàn)閿?shù)據(jù)和組件分離了划煮。當(dāng)組件被recreate,數(shù)據(jù)還是存在LiveData中缔俄,并不會(huì)被銷毀弛秋。

  • 資源共享
    通過繼承LiveData類,然后將該類定義成單例模式俐载,在該類封裝監(jiān)聽一些系統(tǒng)屬性變化蟹略,然后通知LiveData的觀察者,這個(gè)在繼承LiveData中會(huì)看到具體的例子遏佣。

3.11.3 LiveData的基本使用

首先在項(xiàng)目工程里添加依賴
`

// view model
implementation "android.arch.lifecycle:extensions:1.1.0"
annotationProcessor "android.arch.lifecycle:compiler:1.1.0"

`

然后在根目錄工程里添加

allprojects {
    repositories {
        google()
//把這行添加進(jìn)去
        maven { url 'https://maven.google.com' }
        jcenter()
    }
}

最后執(zhí)行使用流程.假設(shè)我現(xiàn)在要實(shí)現(xiàn)下面這樣的一個(gè)需求.
最上面的TextView用于顯示文本,下面的改變數(shù)據(jù)按鈕,點(diǎn)擊時(shí)改變文本的顯示.
最下面的列表是RecyclerView,點(diǎn)擊上面的改變數(shù)據(jù)集合,RecyclerView就顯示對(duì)應(yīng)的數(shù)據(jù)源.
當(dāng)點(diǎn)擊item時(shí),最上面的Textview就顯示點(diǎn)擊的item的內(nèi)容.

具體實(shí)現(xiàn)細(xì)節(jié)

這里只講列表數(shù)據(jù)的關(guān)聯(lián)變化,按鈕點(diǎn)擊的變化比較簡(jiǎn)單,可以看看源碼就明白了.

首先我們?cè)赩iewModel中定義一個(gè)

public final ObservableArrayList<String> mStringObservableArrayList = new
            ObservableArrayList<>();

用于提供列表的數(shù)據(jù).
然后定義一個(gè)系統(tǒng)提供的繼承自LiveData的MutableLiveData.
它持有的數(shù)據(jù)類型為L(zhǎng)ist<String>

 private MutableLiveData<List<String>> mSourceDatas;

提供一個(gè)get方法

  public MutableLiveData<List<String>> getSoureceDatas() {
        if (mSoureceDatas == null) {
            mSoureceDatas = new MutableLiveData<>();
        }
        return mSoureceDatas;
}

在Activity中將LiveData與我們的組件(Activity)關(guān)聯(lián)起來.

  mLiveDataViewModel.getSoureceDatas().observe(this, new Observer<List<String>>() {
            @Override
            public void onChanged(@Nullable List<String> strings) {
                mLiveDataViewModel.addTitleToList(strings);
            }
        });

這個(gè)observe方法的第一個(gè)參數(shù)this,指的是我們的組件Activity,第二個(gè)參數(shù)是可觀察的數(shù)據(jù).
這個(gè)方法的意思是當(dāng)我們觀察的數(shù)據(jù)源改變之后,就會(huì)回調(diào)這個(gè)方法去通知給LiveData.

然后我們實(shí)現(xiàn)addTitleToList這個(gè)方法,

  public void addTitleToList(List<String> titles) {
        mStringObservableArrayList.clear();
        mStringObservableArrayList.addAll(titles);
}

其實(shí)就是修改我們的列表的數(shù)據(jù)源.
因?yàn)檫M(jìn)來列表要顯示初始的數(shù)據(jù),所以給mSoureceDatas設(shè)置初始數(shù)據(jù)

public void setData() {
    List<String> titls = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        titls.add("我是item" + i);
    }
    mSoureceDatas.setValue(titls);
}

然后給更改數(shù)據(jù)源按鈕提供一個(gè)點(diǎn)擊方法.

  public void changeDatas() {
        dataCount++;
        mStringObservableArrayList.clear();
        for (int i = 10 * dataCount; i < 10 + dataCount * 10; i++) {
            mStringObservableArrayList.add("我是改變后的item" + i);
        }
    }

首先清空之前的數(shù)據(jù),,然后添加修改后的數(shù)據(jù).

根據(jù)上面說的,當(dāng)數(shù)據(jù)源改變的時(shí)候,就會(huì)通知LiveData,去執(zhí)行observe方法,而Observable系列的數(shù)據(jù)是跟控件綁定的,它會(huì)自己刷新UI的.
然后在實(shí)現(xiàn)點(diǎn)擊item時(shí),顯示item的值.
首先在Adapter設(shè)置一個(gè)點(diǎn)擊item的回調(diào)監(jiān)聽,這個(gè)就不講了.
然后

   mRvAdatper.setOnclickListener(new RvAdatper.OnItemClickListener() {
            @Override
            public void onItemClick(int position) {
                mLiveDataViewModel.updataContent(position);
            }
        });

當(dāng)點(diǎn)擊item的時(shí)候就去執(zhí)行mLiveDataViewModel.updataContent(position);
它是干嘛的呢?

  public void updataContent(int position) {
        content.set(mStringObservableArrayList.get(position));
    }

content是TextView的數(shù)據(jù)觀察

public final ObservableField<String> content = new ObservableField<>("我是初始值");

當(dāng)點(diǎn)擊item時(shí),去給content設(shè)置item上的內(nèi)容就可以了.

這樣功能就實(shí)現(xiàn)了.我們現(xiàn)在來回顧一下,發(fā)現(xiàn),數(shù)據(jù)與UI是完全隔離的,在ViewModel中,只關(guān)心數(shù)據(jù)的變化,而UI的各種交互點(diǎn)擊在布局文件中聲明好就行了.這為我們省去了很多”垃圾代碼”.最后使用LiveData又幫我們做了數(shù)據(jù)與組件生命周期的優(yōu)化工作.

這些不就是mvvm的魅力嗎?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挖炬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子状婶,更是在濱河造成了極大的恐慌意敛,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膛虫,死亡現(xiàn)場(chǎng)離奇詭異三热,居然都是意外死亡朦肘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偎漫,“玉大人世吨,你說我怎么就攤上這事祖能×尴睿” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵捶障,是天一觀的道長(zhǎng)僧须。 經(jīng)常有香客問我,道長(zhǎng)项炼,這世上最難降的妖魔是什么担平? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任示绊,我火速辦了婚禮,結(jié)果婚禮上暂论,老公的妹妹穿的比我還像新娘面褐。我一直安慰自己,他們只是感情好取胎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布展哭。 她就那樣靜靜地躺著,像睡著了一般闻蛀。 火紅的嫁衣襯著肌膚如雪匪傍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天觉痛,我揣著相機(jī)與錄音役衡,去河邊找鬼。 笑死薪棒,一個(gè)胖子當(dāng)著我的面吹牛手蝎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俐芯,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棵介,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了吧史?” 一聲冷哼從身側(cè)響起邮辽,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贸营,沒想到半個(gè)月后逆巍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莽使,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笙僚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芳肌。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肋层,靈堂內(nèi)的尸體忽然破棺而出亿笤,到底是詐尸還是另有隱情,我是刑警寧澤栋猖,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布净薛,位于F島的核電站,受9級(jí)特大地震影響蒲拉,放射性物質(zhì)發(fā)生泄漏肃拜。R本人自食惡果不足惜痴腌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望燃领。 院中可真熱鬧士聪,春花似錦、人聲如沸猛蔽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曼库。三九已至区岗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毁枯,已是汗流浹背慈缔。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留后众,地道東北人胀糜。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒂誉,于是被迫代替她去往敵國(guó)和親教藻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容