前言
上一篇我们介绍了单独的ViewModel组件的使用实例,Android文档中建议我们LiveData配合ViewModel使用,那么LiveData到底是什么,两者怎么结合使用呢?
LiveData是一个可观察的数据持有者类,具有如下优点:
1,LiveData作为被观察者,数据可以被观察者订阅,当数据有变化时会通知观察者(UI)。
2,减少内存泄漏,因为LiveData能够感知组件的生命周期,页面销毁时他们会自动被移除,不会导致内存溢出,且当我们执行异步的网络请求时,请求到的结果需要更新ui,但是此时activity已经被销毁了,就可以避免nullpointexception异常的产生。
3,组件和数据相关的内容能实时更新,底层数据改变时及时通知ui。
4,数据共享,通过继承LiveData类,然后将该类定义成单例模式,在该类封装监听一些系统属性变化,然后通知LiveData的观察者
1,ViewModel改造:在ViewModel中创建LiveData的实例,如下:
public class MyViewModel extends ViewModel {
private MutableLiveData<Integer> number;
/* MutableLiveData中地数据无法直接赋值,需通过setValue或getaValue赋值或取值 */
/* 获取thumbup的值 */
public MutableLiveData<Integer> getNumber() {
if (number==null){
number = new MutableLiveData<>();
number.setValue(0);
}
return number;
}
public void doSet(int n){
number.setValue(number.getValue()+n);
}
@Override
protected void onCleared() {
super.onCleared();
// 当activity或者fragment销毁时会调用该方法
}
}
2,对应的activity类如下:
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
private Button btn1,btn2;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
tv = findViewById(R.id.tv);
// 获取ViewModel实例
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
// 设置ViewModel中的变量number自己观察自己,当自己发生改变时,更新TextView的显示
// 同理ViewModel中也可以定义其他的成员变量(变量类型均为MutableLiveData,泛型也可以为集合),来动态更新不同的View
// 当视图销毁会自动取消观察订阅,和移除组件,避免了内存泄漏和空指针异常
myViewModel.getNumber().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
// 当数据发生改变时会回调该方法
tv.setText(myViewModel.getNumber()+"");
}
});
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.doSet(1);
}
});
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.doSet(-1);
}
});
}
}
总结:可以看到结合LiveData使用之后,我们不再需要每次数据发生变化都频繁的手动在activity中去设置TextView的值,并且也无需担心内存泄漏和空指针等问题。
观察上面的例子我们发现,activity中存在着大量的控件初始化以及设置点击事件的方法,我们可以借助databinding来进一步简化activity中的逻辑,如下:
1,在app的build.gradle文件中的defaultConfig标签中添加如下:
defaultConfig {
...
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
dataBinding {
enabled true
}
}
2,编译成功后,切换到布局文件,点击左上角的小灯泡处的下拉选项,选择Convert to data binding layout,如图:
会自动生成部分代码,如下:
<?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>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="17sp"
android:textColor="#000"
android:padding="20dp" />
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="+1" />
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-1" />
</LinearLayout>
</layout>
可以看到在我们原有布局的最外层添加了一对layout标签,并且在其中多了一对data标签,
3,当我们将布局进行上述的转换之后,系统会自动为我们生成一个ActivityMainBinding类,类名为layout的文件名+Binding,对应的activity代码如下:
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
ActivityMainBinding activityMainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
// 获取ViewModel实例
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
// 设置ViewModel中的变量number自己观察自己,当自己发生改变时,更新TextView的显示
// 同理ViewModel中也可以定义其他的成员变量(变量类型均为MutableLiveData,泛型也可以为集合),来动态更新不同的View
// 当视图销毁会自动取消观察订阅
myViewModel.getNumber().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
// 当数据发生改变时会回调该方法
activityMainBinding.tv.setText(myViewModel.getNumber()+"");
}
});
activityMainBinding.btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.doSet(1);
}
});
activityMainBinding.btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.doSet(-1);
}
});
}
}
可以看到我们需要注释掉activity中原有的setContentView方法,使用DataBindingUtil.setContentView(this,R.layout.activity_main)的形式来设置并获得ActivityMainBinding的类实例,这样布局文件中的所有控件我们都可以通过ActivityMainBinding的类实例名.对应控件的id的形式来访问了,就不再需要声明控件并findviewbuid了。
如上通过自动生成的ActivityMainBinding类实例就实现了布局文件中控件到activity中的动态绑定,那如果需要将activity中的数据反向绑定到布局文件中的指定控件上,而不是使用传统的在点击事件中的做法那样,我们就需要使用到布局文件中的data标签,在data标签中我们可以使用如下的方式来创建变量:
<data>
<variable
name="data"
type="com.example.viewmodeldemo.MyViewModel" />
</data>
其中对应的data为变量名,type为变量类型,接下来我们就可以在布局文件中写源代码了,需要使用到@{ },其中的大括号里面就可以写java源代码,如下:
<?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="data"
type="com.example.viewmodeldemo.MyViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(data.number)}"
android:textSize="17sp"
android:textColor="#000"
android:padding="20dp" />
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->data.doSet(1)}"
android:text="+1" />
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->data.doSet(-1)}"
android:text="-1" />
</LinearLayout>
</layout>
可以看到我们使用@{ }的形式通过代码进行了值的设置,同时也可以绑定点击事件,只是大括号里面的写法格式不同而已,关于databinding的语法格式可自行查阅资料。
这样一来,在activity中的代码就大大简化了,现在的activity代码如下:
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
ActivityMainBinding activityMainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
// 获取ViewModel实例
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
// 为应布局文件中的变量data进行初始化
activityMainBinding.setData(myViewModel);
// 监听自身数据的变化
activityMainBinding.setLifecycleOwner(this);
}
}
可以看到activity中代码相比最开始的案例大大简化了,使模块化更清晰。