MVVM架构——ViewModel组件


不积跬步,无以至千里,不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!

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;
    }
}

总结

使用方式不同

  1. 数据类继承LiveData,并在set方法中实现postValue方法,然后在ViewModel中使用这个对象。
  2. 数据类不继承LiveData,也不实现postValue方法,在ViewModel中使用MutableLiveData<>对象。(推荐)

观察对象

  1. 必须是LiveData,观察对象必须是数据
  2. 必须是ViewModel中的对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值