目录
不积跬步,无以至千里,不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
ViewModel组件
作用
以可以感知生命周期的方式存储和管理UI中的数据。数据一直存活,就算资源配置改变。
- 因此可以持久化存储数据
- 数据请求造成内存泄露,ViewModel可以解决这个问题(异步回调)
- 划分出Controller中的数据渲染操作
- 用于在Fragment之间共享数据
例子:一个Activity中的计时器销毁后,重启重新计时
计时器控件:Chronometer
组件
通过代码:
根据系统时钟得到开始时间:startTime = SystemClock.elapsedRealtime();
设置开始时间:.setBase(startTime);
启动计时器:.start();
解决办法——旋转屏幕销毁为例
- 不让其重新创建实例
- 通过onSaveInstanceState保存数据,然后重新创建的时候,通过onRestoreInstanceState获取保存的数据,然后设置开始时间
- 采用ViewModel进行保存
默认情况:
xml文件
<Chronometer
android:id="@+id/chronometer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="25dp"/>
java文件
private Chronometer chronometer;
chronometer = findViewById(R.id.chronometer);
long startTime = SystemClock.elapsedRealtime();
chronometer.setBase(startTime);
chronometer.start();
经过实际操作,旋转屏幕后,计时器重新计时。
具体的使用ViewModel解决
- 创建一个自定义ViewModel保存时间
ChronometerViewModel.java
package com.example.myapplication.tool;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModel;
public class ChronometerViewModel extends ViewModel {
//表示可以传入null值
@Nullable
private Long mStartTime;
@Nullable
public Long getStartTime() {
return mStartTime;
}
public void setStartTime(final long startTime) {
this.mStartTime = startTime;
}
}
- 通过工厂反射创建ViewModel
ViewModelFactory.java
package com.example.myapplication.tool;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
public class ViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return modelClass.newInstance(); //使用newInstance反射实例ViewModel,并且传出去
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 使用
- MainActivity
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.widget.Button;
import android.widget.Chronometer;
import com.example.myapplication.tool.ChronometerViewModel;
import com.example.myapplication.tool.ChronometerViewModelFactory;
public class MainActivity extends AppCompatActivity {
private Chronometer chronometer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//在onCreate()方法中开始倒计时
//初始化
chronometer = findViewById(R.id.chronometer);
//拿到ViewModel
ViewModelFactory viewModelFactory = new ViewModelFactory();
ChronometerViewModel chronometerViewModel = new ViewModelProvider(this,viewModelFactory)
.get(ChronometerViewModel.class);
//判断是否初次初始化
if(chronometerViewModel.getStartTime() == null){
//得到时间,设置给ViewModel
long startTime = SystemClock.elapsedRealtime();
chronometerViewModel.setStartTime(startTime);
chronometer.setBase(startTime);
}else{
chronometer.setBase(chronometerViewModel.getStartTime());
}
chronometer.start();
}
}
经过操作验证,旋转屏幕,Home键后,再返回,数据未变,继续计时,没有重新计时。
使用
和LiveData联合使用
ViewModel+LiveData进行数据存储持久化
以存储用户信息为例,点击发送按钮,信息显示在TextView中。
-
先建立一个数据
bean
类- UserInfo
package com.example.myapplication.Model; public class UserInfo { private String name; private int age; //构造方法,初始化对象 public UserInfo(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
-
建立
ViewModel
类,联合LiveData
进行使用- MainViewModel
package com.example.myapplication.activity; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.example.myapplication.Model.UserInfo; public class MainViewModel extends ViewModel { //使用LiveData保存更新数据 MutableLiveData<UserInfo> userData = new MutableLiveData<>(); public void getUserInfo(){ UserInfo user = new UserInfo("李四",20); userData.postValue(user); //postValue连续调用,可能只会调用最后一次设置的数据 // userData.setValue(user); } }
-
创建ViewModel的工厂类同上,工厂类都一样。
-
使用
xml文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.android.lifecycles.step1.ChronoActivity1">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25dp"
android:text="wenskdsak"
android:typeface="serif"/>
<Button
android:id="@+id/btnSend"
android:layout_width="200dp"
android:layout_height="50dp"
android:text="发送"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>
java文件
package com.example.myapplication.activity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.myapplication.R;
import com.example.myapplication.tool.ViewModelFactory;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MainViewModel mainViewModel;
private TextView tvShow;
private Button btnSend;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvShow = findViewById(R.id.text);
btnSend = findViewById(R.id.btnSend);
btnSend.setOnClickListener(this);
ViewModelFactory viewModelFactory = new ViewModelFactory();
mainViewModel = new ViewModelProvider(this, viewModelFactory).get(MainViewModel.class);
//得到userData,作为参数传入,通过其设置,ui回调
mainViewModel.userData.observe(this, userData -> {
tvShow.setText(userData.getName());
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSend:
//点击得到数据
mainViewModel.getUserInfo();
break;
}
}
}
操作验证:点击发送后,数据显示在TextView中,旋转屏幕等配置改变,再返回,数据仍在显示,持久化存储。
在Activity和Fragment之间使用
在Activity和Fragment中获取同一份数据
-
bean类使用上述的用户类
-
新建一个ViewModel,并有一个方法初始化数据。
- SecondViewModel
package com.example.myapplication.fragment; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.example.myapplication.Model.UserInfo; public class SecondViewModel extends ViewModel { public MutableLiveData<UserInfo> userList = new MutableLiveData<>(); public void getInfo(){ UserInfo user = new UserInfo("李四",20); userList.setValue(user); } }
-
在Activity中使用一个按钮,获取数据
-
在Fragment中获取到Activity的ViewModel
-
得到同一个数据,同步更新
- SecondActivity
package com.example.myapplication.activity; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.example.myapplication.R; import com.example.myapplication.fragment.SecondFragment; import com.example.myapplication.fragment.SecondViewModel; import com.example.myapplication.tool.ViewModelFactory; public class SecondActivity extends AppCompatActivity implements View.OnClickListener { SecondViewModel secondViewModel; private TextView tvShowInfo; private Button btnGetInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); //初始化控件 tvShowInfo = findViewById(R.id.tvShowInfo); btnGetInfo = findViewById(R.id.btnGetInfo); btnGetInfo.setOnClickListener(this); //加入Fragment SecondFragment secondFragment = new SecondFragment(); getSupportFragmentManager().beginTransaction().add(R.id.fragment,secondFragment).commit(); getSupportFragmentManager().beginTransaction().show(secondFragment).commit(); ViewModelFactory viewModelFactory = new ViewModelFactory(); secondViewModel = new ViewModelProvider(this,viewModelFactory) .get(SecondViewModel.class); //观察数据是否变化,变化则设置控件内容 secondViewModel.userList.observe(this,userList -> { tvShowInfo.setText(userList.getName()); }); } @Override public void onClick(View v) { switch (v.getId()) { //点击改变数据内容 case R.id.btnGetInfo: secondViewModel.getInfo(); break; } } }
- SecondFragment
package com.example.myapplication.fragment; import android.os.Bundle; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import com.example.myapplication.Model.UserInfo; import com.example.myapplication.R; import com.example.myapplication.tool.ViewModelFactory; public class SecondFragment extends Fragment implements View.OnClickListener{ private SecondViewModel secondViewModel; private TextView tvShowFragment; private Button btnChangeInfo; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_second, container, false); tvShowFragment = root.findViewById(R.id.tvShowFragment); ViewModelFactory viewModelFactory = new ViewModelFactory(); //得到和Activity相同的ViewModel,确保同步更新 secondViewModel = new ViewModelProvider(getActivity(),viewModelFactory) .get(SecondViewModel.class); secondViewModel.userList.observe(getActivity(),userList -> { tvShowFragment.setText(userList.getName()); }); //this代表传入的是当前Fragment的ViewModel //当前的Fragment不存在ViewModel,所以会重新创建 //就无法做到Activity和Fragment同步更新 //因为是两个不同的ViewModel // secondViewModel = new ViewModelProvider(this,viewModelFactory) // .get(SecondViewModel.class); // secondViewModel.userList.observe(this,userList -> { // tvShowFragment.setText(userList.getName()); // }); btnChangeInfo = root.findViewById(R.id.btnChangeInfo); btnChangeInfo.setOnClickListener(this); return root; } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btnChangeInfo: UserInfo user = new UserInfo("小吕",21); secondViewModel.userList.setValue(user); break; } } }
在Activity和Fragment中获取不同数据
只需要将
getActivity()
替换为Fragment.this
即可。
注意:传入Fragment.this,getActivity()不同,得到的ViewModel就不同,因此数据是否需要同时变化,视需求而定传入参数。
在Fragmet和Fragmet之间使用
- 在Activity中放置两个Fragment
- 通过事务加入Fragment
- 两个Fragment中加入文本框显示数据
- 通过点击按钮,改变ViewModel中的数据
- 观察到数据变化,显示在文本框中
- 两个Fragment同步变化,实现了交互
核心代码
package com.example.myapplication.fragment;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import com.example.myapplication.Model.Value;
import com.example.myapplication.R;
import com.example.myapplication.tools.ViewModelFactory;
public class ThirdFragment extends Fragment {
private SeekBarViewModel mSeekBarViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_third, container, false);
ViewModelFactory viewModelFactory = new ViewModelFactory();
mSeekBarViewModel = new ViewModelProvider(getActivity(),viewModelFactory).get(SeekBarViewModel.class);
Button btnSend = root.findViewById(R.id.btnSend);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSeekBarViewModel.getInfo();
}
});
TextView tvShow = root.findViewById(R.id.tvShow);
mSeekBarViewModel.userLiveData.observe(getActivity(),userInfo -> {
tvShow.setText(mSeekBarViewModel.userLiveData.getValue().getName());
});
return root;
}
}
总结
使用方式不同
- 数据类继承LiveData,并在set方法中实现postValue方法,然后在ViewModel中使用这个对象。
- 数据类不继承LiveData,也不实现postValue方法,在ViewModel中使用MutableLiveData<>对象。(推荐)
观察对象
- 必须是
LiveData
,观察对象必须是数据
。 - 必须是
ViewModel
中的对象。