在对应Model的build.gradle中加入:java
android {
dataBinding {
enabled = true
}
}
复制代码
在布局时:android
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
复制代码
原有的布局被一个layout所包裹起来了,其中的<data>
标签内,咱们能够声明须要使用到的变量及其类型。面试
咱们声明一个实体类:markdown
public class User{
private String userName;
private String userCode;
private String userGender;
private String userTel;
public User(String userName, String userCode, String userGender, String userTel) {
this.userName = userName;
this.userCode = userCode;
this.userGender = userGender;
this.userTel = userTel;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserCode() {
return userCode;
}
public void setUserCode(String userCode) {
this.userCode = userCode;
}
public String getUserGender() {
return userGender;
}
public void setUserGender(String userGender) {
this.userGender = userGender;
}
public String getUserTel() {
return userTel;
}
public void setUserTel(String userTel) {
this.userTel = userTel;
}
}
复制代码
而后咱们在页面的逻辑中,须要展现这个类的一些属性,咱们在Databinding中,首先须要创建:视图和Model,即视图和数据之间的联系,让视图可以主动地感知到数据的 变更。架构
因此,咱们在layout.xml中的data标签加入:app
<variable name="displayUserEntity" type="com.example.mvvm1_databinding.User" />
复制代码
name能够本身填写,type则指定为:User实体类。mvvm
咱们也可使用<import>
标签,将整个User包引入到<data>
标签中,以便多个<variable>
使用:ide
<data>
<import type="com.example.mvvm1_databinding.User" />
<variable name="displayUserEntity" type="User" />
<variable name="createNewUserEntity" type="User" />
</data>
复制代码
若是咱们导入了不一样包下的User,能够用alias
属性,指定别名。工具
<data>
<import type="com.example.mvvm1_databinding.User" />
<import alias="UserOfOthers" type="com.example.mvvm1_databinding.others.User" />
<variable name="displayUserEntity" type="User" />
<variable name="createNewUserEntity" type="UserOfOthers" />
</data>
复制代码
接下来,回到只有一个displayUserEntity
和一个<variable>
的状况,咱们在真正的Layout中,建立几个Textview,用于数据的显示:布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="displayUserEntity" type="com.example.mvvm1_databinding.User" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:orientation="vertical" tools:context=".MainActivity">
<TextView android:id="@+id/tv_user_code" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userCode}" />
<TextView android:id="@+id/tv_user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userName}" />
<TextView android:id="@+id/tv_user_gender" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userGender}" />
<TextView android:id="@+id/tv_user_tel" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{displayUserEntity.userTel}" />
</LinearLayout>
</layout>
复制代码
咱们能够为其设置一个默认值,用于占位:android:text="{displayUserEntity.userName,default="预览占位"}"。
:
在真正的布局文件中,咱们能够获取到data
中声明的属性相关的值。
在Activity
中,咱们须要构建相关的实体类,模拟数据的填充:
class MainActivity : AppCompatActivity() {
private lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//怎样去操做displayUserEntity?
initData()
doDataBinding()
}
private fun initData(){
user = User("Jack", "17854124", "男", "400000000")
}
private fun doDataBinding() {
val activityBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
activityBinding.displayUserEntity = user
}
}
复制代码
这样一来,咱们代码中的数据可以被显示在界面上了,而且没有通过繁琐的findViewById(R.id.x).setText(value)
。
至此,咱们就完成了数据从Model流向View层的流程。可是此时,咱们新增一个按钮,声明事件:点击按钮改变user类的数值,并不能作到视图中的数据也被修改,即作不到双向数据绑定(Model即便被修改了,也没法作到View层同步修改)。此时解决的问题,仅仅是省略了繁琐的setText。
所以,如何作到双向数据绑定呢?
咱们指望的是,当Model层的数据被修改时,视图层可以主动感知
到数据的变化,从而本身去更新视图。这和观察者模式自己相契合。
BaseObservable提供了两个方法:notifyChange()和notifyPropertyChanged(),前者会刷新全部的
值域,后者则只更新对应注释的属性,咱们能够在实体类中使用@Bindable标记一个但愿被观察的对象。(若是是私有属性则标记在get方法上)
咱们让Entity实体类继承自BaseObservable类。而后再某个属性上标记@Bindable:
咱们让Entity实体类继承自BaseObservable类。而后再某个属性上标记@Bindable:
public class User extends BaseObservable {
private String userName;
private String userCode;
private String userGender;
private String userTel;
public User(String userName, String userCode, String userGender, String userTel) {
this.userName = userName;
this.userCode = userCode;
this.userGender = userGender;
this.userTel = userTel;
}
@Bindable
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
notifyChange();//只要userName变化了,就刷新全部属性
}
@Bindable
public String getUserCode() {
return userCode;
}
public void setUserCode(String userCode) {
this.userCode = userCode;
notifyPropertyChanged(BR.userCode);//仅仅刷新界面上的userCode属性。
}
public String getUserGender() {
return userGender;
}
public void setUserGender(String userGender) {
this.userGender = userGender;
}
public String getUserTel() {
return userTel;
}
public void setUserTel(String userTel) {
this.userTel = userTel;
}
}
// 注意,kotlin请在build.gradle中的plugins块内加入kapt的配置:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
// 不然@Bindable会报错
复制代码
这样一来,咱们就对userName属性进行了监听,当useName属性发生改变时,视图就能主动感知了。
如今,咱们添加几个方法,用于事件的更新,一样地使用Databinding,咱们在原先的data标签处:
<variable name="eventBinding" type="com.example.mvvm1_databinding.MainActivity.EventBinding" />
复制代码
新增一个eventBinding
属性,该属性的type指向了MainActivity下的一个内部类:
inner class EventBinding{
fun tryLoadNewData(view:View){
this@MainActivity.user.apply {
userName = "Chris"
userCode = "154751512"
userGender = "女"
userTel = "500000000"
}
}
fun updateUserCode(view:View){
//修改UserCode并不会触发所有数据的更新,由于是notifyPropertyChange
this@MainActivity.user.userTel = "5555550"
this@MainActivity.user.userCode = "1"
}
fun updateUserName(view:View){
//修改Name会触发全部的修改,notifyChange
this@MainActivity.user.userGender = "不男不女"
this@MainActivity.user.userName = "abc"
}
fun resetData(view:View){
this@MainActivity.initData()
}
}
复制代码
在布局文件中,新增几个Button,用于触发事件:
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="切换用户" android:onClick="@{eventBinding::tryLoadNewData}" tools:ignore="HardcodedText" />
<Button android:id="@+id/btn_update_user_code" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新用户Code,只更新了本身" android:onClick="@{eventBinding::updateUserCode}" >
</Button>
<Button android:id="@+id/btn_update_user_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新用户Name,且会触发全部属性在界面上的更新" android:onClick="@{eventBinding::updateUserName}" >
</Button>
复制代码
最后,咱们在doDataBinding方法中,设置dataBinding的eventBinding
属性便可:
private fun doDataBinding() {
activityBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
activityBinding.displayUserEntity = user
eventBinding = EventBinding()
activityBinding.eventBinding = eventBinding
}
复制代码
这样一来,咱们就能够看清楚notifyChanged
和notifyPropertyChanged
的区别了。
咱们也能够设置一个监听器:
user.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
Log.d("MVVM",propertyId.toString())
}
})
复制代码
//当有被@Bindable修饰的属性被修改时,此方法会被回调。打印的是被修饰变量的Id,而不是所有被更新的属性Id各打印一次。例如更新userTel userName userCode时,只有后面两个属性的值会被打印出来。
对一些基本数据类型的封装,也能够填入泛型参数。
用于替代原生的 List
和 Map
,分别是 ObservableList
和 ObservableMap
,当其包含的数据发生变化时,绑定的视图也会随之进行刷新。在视图中,采用list[index]
或者是map[key]
来访问。
在上文的DataBinding工具的介绍中,咱们知道,咱们能够很轻易地创建:View与视图Layout的链接,咱们的后台数据利用Observe能够轻松地构建响应式
的布局。实际上,这已经解决了MVC、MVP中的一个很是之繁琐的一个操做:findViewById、setText,咱们只须要在Bean中的数据更新后,调用notify相关的方法便可更新到视图。这一切都基于DataBinding。Databinding自己更像是Activity和XML文件的黏合剂,使得View层的功能更加明确,没必要再大量书写setText等修改视图的代码。
可是,这个步骤能不能再简单一点?回顾一下咱们在面试过程当中复习了无数遍的MVP和MVVM的区别,MVVM分三层,M、V、VM,M和V都好说,Presenter被替换成了VM,VM即ViewModel。
原先的Presenter任务繁重的缘由很大程度上是由于Presenter做为"主持人",须要持有V、M的引用,View层会利用Presenter去调用Model层的代码去取数据,而Model层又会使用Presenter的方法返回数据到视图。一个简单的功能,在Presenter中却要两个方法共同处理。
在引入VM后,得益于观察者模式,界面能够直接监听Model数据的变化,这样一来,VM层相对于原先的P层,进化了一点:只须要单向地处理View指派给Model层的任务了,这样减小了大量的代码。官方ViewModel类的出现很大程度上是为了规范对MVVM架构的实现。另外,它的一个派生类:AndroidViewModel能够得到运行时的Application。
咱们直接继承自ViewModel便可使用:
//别忘了添加依赖
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
public class MyViewModel extends ViewModel {
private final MutableLiveData<User> mUser = new MutableLiveData<>(new User(
"Jack",
"170000",
"男",
"12939487210"
));
private final MutableLiveData<Integer> videoId = new MutableLiveData<>(-1);
private final MutableLiveData<String> videoTitle = new MutableLiveData<>("Example");
public MutableLiveData<User> getmUser() {
return mUser;
}
}
复制代码
//MainActivity中
private fun initViewModel() {
mMyViewModel = ViewModelProvider(this).get(MyViewModel::class.java);
//观察这个LiveData<User>,一旦产生属性变化,那么就回调到此处。
mMyViewModel.getmUser().observe(this, Observer {
Log.d("MVVM",it.toString())
})
}
复制代码
咱们将user对象从Activity中搬运到了ViewModel中,这样一来,存储变量的任务就交给了ViewModel,而不是Activity。
即生命周期,若是某个组件具备生命周期,那么能够实现接口:LifecycleOwner
,即说明本身是一个生命周期的持有者,重写
@NonNull
Lifecycle getLifecycle();
复制代码
得到生命周期。
而lifecycle自己才是真正的生命周期,其中枚举了State的全部状态:
public enum State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
}
复制代码
以及全部可能发生的相关ON事件:
public enum Event {
ON_CREATE,
ON_START,
ON_RESUME,
ON_PAUSE,
ON_STOP,
ON_DESTROY,
ON_ANY
}
复制代码
咱们能够自定义类继承自:LifecycleObserver来监听生命周期:
package com.example.lifecycle;
import android.util.Log;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import java.util.concurrent.Semaphore;
public class MyUtil implements LifecycleObserver {
private String TAG = "MY_UTIL";
private int count = 0;
private Thread runner;
private Semaphore semaphore = new Semaphore(1);//定义信号量
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
private void onCreate() throws InterruptedException {
Log.d(TAG,"ON_CREATE");
runner = new Thread(() -> {
while (true) {
try {
Thread.sleep(500);
semaphore.acquire();//上锁
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, String.valueOf(count++));
semaphore.release();//释放锁
}
});
semaphore.acquire();
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private void onResume(){
Log.d(TAG,"ON_RESUME");
if(!runner.isAlive()){
runner.start();
}
semaphore.release();//释放掉锁
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private void onPause() throws InterruptedException {
Log.d(TAG,"ON_PAUSE");
semaphore.acquire();//得到锁
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private void onDestroy(){
Log.d(TAG,"ON_DESTROY");
runner.interrupt();
}
}
//MainActivity.java的onCreate中
getLifecycle().addObserver(new MyUtil());
复制代码
这是一个很简单的例子,在onResume
时,开启一个线程计数并输出,退出到桌面时,回调到onPause
时,暂停计数输出,若是销毁则终止线程的执行。代码自己很简单,可是有一个要特别注意的点是,若是咱们将addObserver
的操做放在onStart()
中,咱们仍然可以收到ON_CREATE和ON_START事件(效果很像粘性事件)。
package com.example.lifecycle;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
getLifecycle().addObserver(new MyUtil());//
}
}
复制代码
在MVP中,View层持有P层引用,同时:
在引入DataBinding + ViewModel + LiveData后:
只须要监听数据,数据基本处理交给VM层,VM层去Call Model层的方法。
DataBinding + LiveData解决
ViewModel解决
这一点不变
至于恢复数据等操做,实际上ViewModel中存储LiveData可以很好地帮助咱们处理数据,即便横屏重建也不会致使数据丢失。
因此,原先须要集中写在MainActivity.java中的五个功能点,如今只剩下1.5个:
进一步引入lifecycle
之后,管理生命周期也能够剥离出来,这样一来,View层的职责就完彻底全变成了:
这是很是理想的一个View层模型。这样咱们View层相关的类有:
//layout.xml
class MainActivity extends AppcompatActivity(){}
//1.响应用户输入 ->对应4
public class MyViewModel extends ViewModel{}
//1.存储变量 ->对应3
//2.请求Model层的方法,并在回调中修改LiveData的值,界面值因为DataBinding能够获得同步修改。 -> 对应一、2
public class MyLifecycleObserver implements LifecycleObserver{}
//1.监听生命周期 -> 对应5
复制代码
这样一来,相比较MVP层的P层,View层的职责更加清晰,P层的基于接口的代码量也大大降低(不须要再处理数据从M流向V层的接口方法了)。