视图模型,主要是处理网络或者数据库得到的数据,为activity或者fragment准备好数据,可结合liveData使用,特点是生命周期比Activity长。
优点
- 解决屏幕旋转后数据丢失问题
- 避免异步调用导致的内存泄漏问题
注意点
- 不允许传入上下文Context
- 若需要上下文,则继承AndroidViewModel
生命周期图
ViewModel将一直留在内存中,直到限定其存在时间范围的Lifecycle 永久消失:对于Activity,是在 Activity完成时;而对于Fragment,是在Fragment分离时。
屏幕旋转后数据依然有
实例代码
MainActivity.java
package com.elaine.testviewmodel;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
public class MainActivity extends AppCompatActivity {
//使用viewModel
private MainViewModel mainViewModel;
private TextView tvContent;
//不使用viewModel
private int contentTwo;
private TextView tvContentTwo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testViewModel();
noViewModel();
}
/**
* 使用到ViewModel的变量
*/
private void testViewModel() {
mainViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MainViewModel.class);
tvContent = findViewById(R.id.tv_content);
tvContent.setText(mainViewModel.content + "");
}
/**
* 常规变量
*/
private void noViewModel() {
tvContentTwo = findViewById(R.id.tv_content_two);
contentTwo = 0;
tvContentTwo.setText(contentTwo + "");
}
/**
* 数字增大方法
*
* @param view
*/
public void add(View view) {
tvContent.setText(++mainViewModel.content + "");
tvContentTwo.setText(++contentTwo + "");
}
}
MainViewModel.java
package com.elaine.testviewmodel;
import androidx.lifecycle.ViewModel;
/**
* author: elaine
* date: 2021/5/8
*/
public class MainViewModel extends ViewModel {
public int content = 0;
}
activity_main.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=".MainActivity">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_content_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_content" />
<Button
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="add"
android:text="add"
app:layout_constraintBottom_toTopOf="@+id/tv_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
操作流程
打开APP,然后旋转手机,然后再旋转手机,然后点击加按钮,然后再旋转手机
操作结果
第一回旋转手机,数据都是0,第二回旋转手机,数据都是0,在点击3下加按钮后再旋转手机,使用了viewModel的数据为3,而不使用viewModel的是0。
多个fragment数据共享
注意点
获取ViewModel时,传入的都是它们宿主Activity。
实例代码
TestFragmentActivity.java
package com.elaine.testviewmodel.testfragment;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.elaine.testviewmodel.R;
public class TestFragmentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_fragment);
}
}
activity_main.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=".testfragment.TestFragmentActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_end="366dp"
app:layout_constraintTop_toTopOf="parent" />
<fragment
android:id="@+id/up"
android:name="com.elaine.testviewmodel.testfragment.UpFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<fragment
android:id="@+id/bottom"
android:name="com.elaine.testviewmodel.testfragment.BottomFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline4" />
</androidx.constraintlayout.widget.ConstraintLayout>
TestFragmentViewModel.java
package com.elaine.testviewmodel.testfragment;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
/**
* BottomFragment和UpFragment公有的ViewModel
* author: elaine
* date: 2021/5/10
*/
public class TestFragmentViewModel extends ViewModel {
private MutableLiveData<Boolean> isOpen;
public MutableLiveData<Boolean> getIsOpen() {
if (isOpen == null) {
isOpen = new MutableLiveData<>();
isOpen.setValue(false);
}
return isOpen;
}
}
UpFragment.java
package com.elaine.testviewmodel.testfragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.elaine.testviewmodel.R;
/**
* 上面的Fragment,内有一个开关控件
*/
public class UpFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_up, container, false);
Switch swUp = view.findViewById(R.id.sw_up);
//注意,这里要用getActivity(),而不是this
TestFragmentViewModel viewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(TestFragmentViewModel.class);
//公共viewModel数据变化监听
viewModel.getIsOpen().observe(this, swUp::setChecked);
//控件操作监听
swUp.setOnCheckedChangeListener((buttonView, isChecked) -> viewModel.getIsOpen().setValue(isChecked));
return view;
}
}
fragment_up.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=".testfragment.UpFragment">
<Switch
android:id="@+id/sw_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开关"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
BottomFragment.java
package com.elaine.testviewmodel.testfragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.elaine.testviewmodel.R;
/**
* 下面的Fragment,内有一个开关控件
*/
public class BottomFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_bottom, container, false);
Switch swBottom = view.findViewById(R.id.sw_bottom);
//注意,这里要用getActivity(),而不是this
TestFragmentViewModel viewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(TestFragmentViewModel.class);
//公共viewModel数据变化监听
viewModel.getIsOpen().observe(this, swBottom::setChecked);
//控件操作监听
swBottom.setOnCheckedChangeListener((buttonView, isChecked) -> viewModel.getIsOpen().setValue(isChecked));
return view;
}
}
fragment_bottom.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=".testfragment.BottomFragment">
<Switch
android:id="@+id/sw_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开关"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
操作流程
点击上面的开关,点击下面的开关
操作结果
上面的开关随下面的开关变换,同理下面的开关。