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)行效果如圖
我們并沒有調(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)行效果:
可以看到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)行效果如下圖:
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的魅力嗎?