安卓系列之Jetpack架构(AAC):ViewModel基础篇

146 阅读2分钟

视图模型,主要是处理网络或者数据库得到的数据,为activity或者fragment准备好数据,可结合liveData使用,特点是生命周期比Activity长。

优点

  1. 解决屏幕旋转后数据丢失问题
  2. 避免异步调用导致的内存泄漏问题

注意点

  1. 不允许传入上下文Context
  2. 若需要上下文,则继承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(thisnew 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>

操作流程

点击上面的开关,点击下面的开关

操作结果

上面的开关随下面的开关变换,同理下面的开关。

项目github地址

github.com/ElaineTaylo…

若帅哥美女对该系列文章感兴趣,可微信搜索公众号(木子闲集)关注更多更新文章哦,谢谢~