1.ViewModel
1.ViewModel的用法
ViewModel的作用:
专门用于存放与界面相关的数据,帮助Activity分担一部分工作;
生命周期与Activity不同,保证在手机屏幕发生旋转的时候不会被重新创建
1.添加依赖
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
2.给MainActivity创建一个对应的MainViewModel类
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
public int counter = 0;
}
3.修改布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32sp" />
<Button
android:id="@+id/plusOneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Plus One" />
</LinearLayout>
4.修改MainActivity
在Android开发中如果出现android.content.res.Resources$NotFoundException: String resource ID #0x1这样的错误,你想也不用想,一定是Textview控件显示数据出了问题:mTextview.setText(这里的传入的数据一定写成int类型了)。我们需要做的是eg:mTextview.setText(1+""),也就是参数转化成字符串
————————————————
版权声明:本文为CSDN博主「合抱之木,生于毫末,九层之台,起于累土」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014133119/article/details/80989479
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public MainViewModel mainViewModel;
private TextView infoText;
private Button plusOneBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
plusOneBtn = findViewById(R.id.plusOneBtn);
infoText = findViewById(R.id.infoText);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
plusOneBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mainViewModel.counter++;
refreshCounter();
}
});
refreshCounter();
}
private void refreshCounter(){
//这里一定要传一个String类型的参数,否则启动应用的时候会报错
infoText.setText(mainViewModel.counter+"");
}
}
2.向ViewModel传递参数
现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么之前的计数就会被清零了
请注意:现在按返回键后,onDestroy方法不会执行,所以在onBackPressed方法里手动finish一下活动。Android 12 Ondestroy不执行 - 简书 (jianshu.com)
1.修改MainViewModel
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
public int counter = 0;
public MainViewModel(int counter) {
this.counter = counter;
}
}
2.新建一个MainViewModelFactory类,并让它实现ViewModelProvider.Factory接口
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
public class MainViewModelFactory implements ViewModelProvider.Factory {
private final int countReserved;
public MainViewModelFactory(int countReserved) {
this.countReserved = countReserved;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MainViewModel(countReserved);
}
}
3.界面上添加一个清零按钮,方便用户手动将计数器清零
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32sp" />
<Button
android:id="@+id/plusOneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Plus One" />
<Button
android:id="@+id/clearBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Clear" />
</LinearLayout>
4.修改MainActivity
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "zhangke";
public MainViewModel mainViewModel;
public SharedPreferences sp;
private TextView infoText;
private Button plusOneBtn, clearBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate方法");
setContentView(R.layout.activity_main);
plusOneBtn = findViewById(R.id.plusOneBtn);
clearBtn = findViewById(R.id.clearBtn);
infoText = findViewById(R.id.infoText);
plusOneBtn.setOnClickListener(this);
clearBtn.setOnClickListener(this);
sp = getPreferences(Context.MODE_PRIVATE);
int countReserved = sp.getInt("count_reserved", 0);
mainViewModel = new ViewModelProvider(this, new MainViewModelFactory(countReserved)).get(MainViewModel.class);
refreshCounter();
}
private void refreshCounter() {
infoText.setText(mainViewModel.counter + "");
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.plusOneBtn:
mainViewModel.counter++;
Log.d(TAG, mainViewModel.counter + "");
refreshCounter();
break;
case R.id.clearBtn:
mainViewModel.counter = 0;
refreshCounter();
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy方法");
SharedPreferences.Editor edit = sp.edit();
edit.putInt("count_reserved", mainViewModel.counter);
edit.apply();
}
@Override
public void onBackPressed() {
super.onBackPressed();
Log.d(TAG, "onBackPressed");
finish();
}
}
2.Lifecycle
可以让任何一个类都能轻松感知到Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理
1.新建一个MyObserver类,并让它实现LifecycleObserver接口
我们在方法上使用了@OnLifecycleEvent注解,并传入了一种生命周期事件。生
命周期事件的类型一共有7种:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、
ON_STOP和ON_DESTROY分别匹配Activity中相应的生命周期回调;另外还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期回调
import android.util.Log;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void activityStart() {
Log.d("zhangke_MyObserver", "activityStart");
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void activityStop() {
Log.d("zhangke_MyObserver", "activityStop");
}
}
2.在MainActivity的onCreate方法中添加
首先调用LifecycleOwner的getLifecycle()方法,得到一个Lifecycle对象,然后调用它
的addObserver()方法来观察LifecycleOwner的生命周期,再把MyObserver的实例传进去
就可以了只要你的Activity是继承自AppCompatActivity的,或者你的Fragment是继承自
androidx.fragment.app.Fragment的,那么它们本身就是一个LifecycleOwner的实例,
这部分工作已经由AndroidX库自动帮我们完成了
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate方法");
setContentView(R.layout.activity_main);
...
getLifecycle().addObserver(new MyObserver());
}
目前MyObserver虽然能够感知到Activity的生命周期发生了变化,却没有办法主动获知当前的生命周期状态。
要解决这个问题也不难,只需要在MyObserver的构造函数中将Lifecycle对象传进来即可
3.LiveData
1.基本用法
LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生
变化的时候通知给观察者
1.修改MainViewModel
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
public final MutableLiveData<Integer> counter = new MutableLiveData<Integer>();
public MainViewModel(int counter) {
this.counter.setValue(counter);
}
public final void plusOne() {
Integer count = counter.getValue();
if (count == null) {
count = 0;
}
counter.setValue(count + 1);
}
public final void clear() {
counter.setValue(0);
}
}
2.修改MainActivity
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "zhangke";
public MainViewModel mainViewModel;
public SharedPreferences sp;
private TextView infoText;
private Button plusOneBtn, clearBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate方法");
setContentView(R.layout.activity_main);
plusOneBtn = findViewById(R.id.plusOneBtn);
clearBtn = findViewById(R.id.clearBtn);
infoText = findViewById(R.id.infoText);
plusOneBtn.setOnClickListener(this);
clearBtn.setOnClickListener(this);
sp = getPreferences(Context.MODE_PRIVATE);
int countReserved = sp.getInt("count_reserved", 0);
mainViewModel = new ViewModelProvider(this, new MainViewModelFactory(countReserved)).get(MainViewModel.class);
mainViewModel.counter.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
infoText.setText(integer+"");
}
});
getLifecycle().addObserver(new MyObserver());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.plusOneBtn:
mainViewModel.plusOne();
Log.d(TAG, mainViewModel.counter.getValue() + "");
break;
case R.id.clearBtn:
mainViewModel.clear();
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy方法");
SharedPreferences.Editor edit = sp.edit();
edit.putInt("count_reserved", mainViewModel.counter.getValue()!=null?mainViewModel.counter.getValue():0);
edit.apply();
}
@Override
public void onBackPressed() {
super.onBackPressed();
Log.d(TAG, "onBackPressed");
finish();
}
}
MainViewModelFactory和MyObserver不变
计数器功能同样是可以正常工作的。不同的是,现在我们的代码更科学,也更合理,而且不用担心ViewModel的内部会不会开启线程执行耗时逻辑。不过需要注意的是,如果你需要在子线程中给LiveData设置数据,一定要调用postValue()方法,而不能再使用setValue()方法,否则会发生崩溃
2.需要优化的地方
以上就是LiveData的基本用法。虽说现在的写法可以正常工作,但其实这仍然不是最规范的
LiveData用法,主要的问题就在于我们将counter这个可变的LiveData暴露给了外部。这样
即使是在ViewModel的外面也是可以给counter设置数据的,从而破坏了ViewModel数据的
封装性,同时也可能带来一定的风险。
1.修改MainViewModel,其实就是将变量声明为private,然后加一个get方法
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
private final MutableLiveData<Integer> _counter = new MutableLiveData<Integer>();
public LiveData<Integer> get_counter() {
return _counter;
}
public MainViewModel(int counter) {
this._counter.setValue(counter);
}
public final void plusOne() {
Integer count = _counter.getValue();
if (count == null) {
count = 0;
}
_counter.setValue(count + 1);
}
public final void clear() {
_counter.setValue(0);
}
}
2.修改MainActivity
将mainViewModel.counter修改为mainViewModel.get_counter()
3.map和switchMap
看map()方法,这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的
LiveData进行转换
1.创建数据类User
import java.util.Objects;
public class User {
private String firstName;
private String lastName;
private int age;
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(firstName, user.firstName) && Objects.equals(lastName, user.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName, age);
}
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
}
2.修改MainViewModel
import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
private final MutableLiveData<Integer> _counter = new MutableLiveData<Integer>();
private final MutableLiveData<User> userLiveData = new MutableLiveData<>();
public LiveData<Integer> get_counter() {
return _counter;
}
public MainViewModel(int counter) {
final LiveData<Object> userName = Transformations.map(userLiveData, new Function<User, Object>() {
@Override
public Object apply(User input) {
return input.getFirstName() + input.getLastName();
}
});
this._counter.setValue(counter);
}
public final void plusOne() {
Integer count = _counter.getValue();
if (count == null) {
count = 0;
}
_counter.setValue(count + 1);
}
public final void clear() {
_counter.setValue(0);
}
}
前面我们所学的所有内容都有一个前提:LiveData对象的实例都是在ViewModel中创建的。然而在实际的项目中,不可能一直是这种理想情况,很有可能ViewModel中的某个LiveData对象是调用另外的方法获取的。
这个时候,switchMap()方法就可以派上用场了。正如前面所说,它的使用场景非常固定:如
果ViewModel中的某个LiveData对象是调用另外的方法获取的,那么我们就可以借助
switchMap()方法,将这个LiveData对象转换成另外一个可观察的LiveData对象。
未完待续