MVVM是Google最新推出的架构,目的帮助设计鲁棒性高,易测试,易维护的app。涉及到LiveData,ViewModel和DataBinding。接下来分别介绍这些概念。
在介绍这些概念之前,先把这些添加依赖,配置如下是:
dependencies {
def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
// alternatively - just LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
// AndroidX libraries use this lightweight import for Lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // For Kotlin use lifecycle-reactivestreams-ktx
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}
android.useAndroidX=true
android.enableJetifier=true
android {
...
dataBinding {
enabled = true
}
}
1.LiveData
在给定的生命周期中,LiveData 是可被观察的数据持有类。(也就是说他是有生命周期的,会随着生命周期而消亡)。
这意味着观察者和生命周期拥有者是关联的,只有在LifecycleOwener在激活状态下,这个观察者才会通知包裹数据的修改。
LifecycleOwner的状态为STARTED或者RESUMED,表示出于激活状态。observer通过函数observeForever(Observer) 添加的都是处于激活状态并且会始终通知修改。对于这些observers,我们需要手动通过removeObserver(Observer)移除。
如果被添加到Lifecycle中的observer自动消亡了,那么对应的Lifecycle也变成DESTROYED状态。
对于activities 和 fragments特别有用,能够安全观察数据,不用担心泄露:当他们消亡时,自动解注册。
此外,LiveData有onActive() 和 onInactive()两个方法获,以便当激活Observers个数在0和1变化时通知。这允许LiveData在没有任何正在观察的观察者时释放任何重型资源。
这个类设计的目的保存ViewModel数据各个字段,但是也能用于你的应用中的不同模块饭共享数据,在解耦的方式下。
函数 | 用途 |
---|---|
onActive() | 当激活的观察者从0变成1时调用 |
onInactive() | 当激活的观察者从1变成0时调用 |
postValue(T value) | 在主线程中设置值 |
setValue(T value) | 设置值 |
如何共享数据呢?
ViewModelProviders.of(activity).get(TestViewModel.class);只要activity共用即可。
-
MediatorLiveData
考虑下面场景:我们有两个LiveData 实例,名字为 liveData1 和 liveData2,我们像把这个两个合并成一个对象:liveDataMerger。然而liveData1 和 liveData2 就变成MediatorLiveData liveDataMerger 数据源,每次其中的一个onChanged调用,我们在liveDataMerger设置一个新值。
LiveData liveData1 = ...;
LiveData liveData2 = ...;
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
2.ViewModel
ViewModel是个为activity或者fragment准备和管理数据的类,它还处理Activity/Fragment与应用程序其余部分的通信(比如,叫做业务逻辑类)。
ViewModel始终和活动相关(fragment/activity),只要活动还在它就在。
换句话说,这意味着ViewModel是不会被销毁的,如果它的拥有着发生配置变化(例如,屏幕旋转),新的拥有者会重新连接现存的ViewModel.
ViewModel的目的是为了保存activity/fragment必要数据的。在ViewModel中,Activity 或者Fragment能够观察到数据的变化。ViewModels一般通过LiveData或者Android Data Binding 暴露数据的.
ViewModel职责时管理UI数据。它从不访问view层次结构或者把数据保存回activity和fragment。
值得注意的是:ViewModel 对象比view和lifecycleowner活得长,所以ViewModel绝不能引用View和LifecycleOwner相关的东西。这种设计另外一个目的:在不知道view和Lifecycle的情况下,写ViewModel,解耦和。
ViewModel如何使用呢,首先,新建一个ViewModel类,其次,在UserActivity中进行注册,最后,更新UI。VIewModel可以看作业务逻辑处理地方。
public class UserModel extends ViewModel {
public final LiveData<User> userLiveData = new LiveData<>();
public UserModel() {
// trigger user load.
}
void doAction() {
// depending on the action, do necessary business logic calls and update the
// userLiveData.
}
}
public class UserActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user_activity_layout);
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
viewModel.userLiveData.observer(this, new Observer() {
@Override
public void onChanged(@Nullable User data) {
// update ui.
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.doAction();
}
});
}
}
3.DataBinding
使用DataBinding的布局文件和普通的布局文件略有不同,通过layout标签。布局文件的结构大致如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
layout 是DataBinding格式,没啥好说的。
data标签,是引用数据的,是UI的数据来源。variable标签下name为变量名,在布局文件中使用的名字,而type则是真是数据的来源。
剩下的部分就是布局文件,通过@{}方式进行引用。
假定数据model如下:
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Binding Adapters
Binding adapters 负责调用合适的框架进行设置值。一个属性设置的例子,像调用setText()方法,显示文本,另外监听事件设置的例子,像调用setOnClickListener()方法。
Data Binding Library 允许你指定方法来设置值,提供你自己的绑定逻辑,并通过使用适配器来指定返回的数据类型。
看到这,不是感觉不知所云,通俗的说,就是对方法或者属性进行绑定自定义逻辑,比如自定义一个属性,并给这个属性绑定自定义逻辑。
Binding Adapters 基本使用:
通过自定义属性并绑定自定义逻辑来说明如何使用的,总共分三步:
1.定义属性:
在values/attrs中配置如下:
<resources>
<declare-styleable name="View">
<attr name="onClickCommand" />
</declare-styleable>
</resources>
其中name设置为View,那么所有的view都具有该属性,设置之后,所有的view都有一个属性:onClickCommand,textview,imageview,都具有该属性。
2.绑定事件逻辑
属性定义好之后,怎么将自定义的属性和事件绑定到一起,这就要用到 @BindingAdapter,我们将属性onClickCommand与单击事件进行绑定,通过下面代码进行绑定。
public class ViewAdapter {
@BindingAdapter(value = {"onClickCommand"}, requireAll = false)
public static void onClickCommand(View view, final BindingCommand<String> bindingCommand) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (bindingCommand != null) {
bindingCommand.execute("haochen");
}
Toast.makeText(v.getContext(), "onclick", Toast.LENGTH_SHORT).show();
}
});
}
}
3.在布局文件中使用
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:binding="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.iflytek.myapplication.TestViewModel" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lottery_animlist"
android:text="lsfdjlksfjdlksjflkdjlksfjdlkj"
binding:onClickCommand="@{viewmodel.mStringBindingCommand}" />
</LinearLayout>
</layout>
上面的TestViewModel 如下:
public class TestViewModel extends ViewModel {
MutableLiveData<User> mStringMutableLiveData = new MutableLiveData<>();
public LiveData<User> getData() {
User mUser = new User("first", "second");
mStringMutableLiveData.setValue(mUser);
return mStringMutableLiveData;
}
public BindingCommand<String> mStringBindingCommand = new BindingCommand<>(new CommandAction<String>() {
@Override
public void call(String s) {
Log.i("test", s);
}
});
}
需要注意的是:
需要在binding下viewmodel,否则事件的回调不执行,原因是因为bindingCommand 没有实例化导致的,添加类似代码即可。
mTestViewModel = ViewModelProviders.of(this).get(TestViewModel.class);
mActivityMainBinding.setViewmodel(mTestViewModel);