欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Jetpack 之 ViewModel

程序员文章站 2022-06-09 23:51:10
...

简介:

官方解释:ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。
ViewModel 类让数据可在发生屏幕旋转等配置更改后继续存在。
其实简单来讲就是解决下面的问题:

1、Activity配置更改重建时(比如屏幕旋转)保留数据;
2、UI组件(Activity与Fragment、Fragment与Fragment)间实现数据共享。

第一种情况下我们一般是通过onSaveInstanceState保存数据,当activity页面重新启动时再
通过onCreate()方法的bundle取出,但如果数据量较大的话,操作会变得复杂,性能也会受一定
的影响。

第二种情况下我们在不用ViewModel的情况我们页面共享数据,如果新增一个共享数据,那我们每个
页面都需要重新声明新增的共享数据,还有就是当我们共享数据发生修改时,其他页面无法及时的感知到,
需手动添加观察者模式,而ViewModel结合LiveData就可以轻松实现这一点。

这里插一句题外,MVVM与MVP这两个架构最大的区别就是用ViewModel(简称VM)代替了原来
的P层(Presenter),这里的VM就是ViewModel。一句话概括它的特点---对数据状态的持有和维护。换言之,
它将原来Presenter层关于数据的逻辑运算与处理统一放到了VM中,而剩余的V层的操作建议使用Databinding,
从而形成最为简洁高效的MVVM架构。

话不多说,我们先来看第一种操作,我是写了一个页面,点击按钮更新数据,更新完毕后我们旋转一下屏幕,然后
看看结果,先看下代码:

简单的xml布局代码:

<?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:gravity="center"
    android:orientation="vertical">


    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是原始数据"
        />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击修改原始数据"/>

</LinearLayout>

接下来是页面代码,简单的操作,点击按钮更新数据。然后我们看看运行过程

public class ViewModelActivity extends AppCompatActivity {

    private TextView tv;
    private Button btn;

    private Student stu;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewmodel);
        tv = findViewById(R.id.tv);
        btn = findViewById(R.id.btn);

        initView(stu);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stu = new Student("白展堂", 18);
                tv.setText("我是已经修改的数据,我的名字是"+stu.getName());
            }
        });

    }
    private void initView(Student stu) {
        if (stu != null){
            tv.setText("我是已经修改的数据,我的名字是"+stu.getName());
        }

    }
}

运行结果:如下,可以正常显示数据。

Android Jetpack 之 ViewModel

接下来,我们把屏幕旋转了一下,变成以下内容

Android Jetpack 之 ViewModel

我们发现我们刚才操作的数据更新没有改变,而是变成原来的默认数据。这里就出现了我们上面所说的第一种情况,而ViewModel的出现可以为我们保存Activity配置更改重建时(比如屏幕旋转)的数据;那么,我们来看一下ViewModel怎么为我们工作的,同样,先看一下代码,首先我们操作的类需要继承ViewModel

public class PersonViewModel extends ViewModel {
    Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
}

接下来是页面代码:

public class ViewModelActivity extends AppCompatActivity {

    private TextView tv;
    private Button btn;
    private PersonViewModel personViewModel;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewmodel);
        tv = findViewById(R.id.tv);
        btn = findViewById(R.id.btn);
        personViewModel = ViewModelProviders.of(ViewModelActivity.this).get(PersonViewModel.class);
        initView(personViewModel);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Student stu = new Student("白展堂", 18);
                personViewModel.setStudent(stu);
                tv.setText("我是已经修改的数据,我的名字是"+personViewModel.getStudent().getName());
            }
        });

    }
    private void initView(PersonViewModel personViewModel) {
        if (personViewModel.getStudent() != null){
            tv.setText("我是已经修改的数据,我的名字是"+personViewModel.getStudent().getName());
        }

    }

}

我们看一下运行结果和旋转后的结果:

Android Jetpack 之 ViewModel

Android Jetpack 之 ViewModel

我们可以看到,即使我们旋转了屏幕,数据依然存在。这就是我们要的效果。

接下来我们看下它的第二个用途,共享数据,Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设我们有一个 Fragment,在该 Fragment 中,用户从修改了显示内容,还有另一个 Fragment,也是有一个单独模板用于显示该项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。
此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
这种情况,我们就可以用到ViewModel配合LiveData来处理了,我们接着往下看。
我这个例子是用tab控制两个不同的Fragment,然后它们共享了一个资源数据。我们看下代码,首先是操作页面简单
的布局xml:

<?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">
    <android.support.design.widget.TabLayout
        android:id="@+id/mytab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        ></android.support.design.widget.TabLayout>
    <android.support.v4.view.ViewPager
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/mViewPager"
        ></android.support.v4.view.ViewPager>
</LinearLayout>

然后就是第一个FragmentOne的布局文件:

<?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"
    android:gravity="center">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ONE"
        android:gravity="center"
        android:textSize="30sp"
        android:textStyle="bold"
        />
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="修改"/>


</LinearLayout>

接下来是第二个FragmentTwo的布局文件:

<?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"
    android:gravity="center">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TWO"
        android:gravity="center"
        android:textSize="30sp"
        android:textStyle="bold"
        />
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="修改的值:"
        android:gravity="center"
        android:textSize="16sp"
        android:textStyle="bold"
        />

</LinearLayout>

好了,到这里我们页面的布局就好了。

然后我们看一下我们页面共享的Model代码,继承于ViewModel,配合LiveData进行数据更新监听。

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Person> person = new MutableLiveData<Person>();
    public void setPerson(Person p){
        person.setValue(p);
    }
    public LiveData<Person> getPerson(){
        return person;
    }
}

到这里之后我们看一下具体的页面和操作代码,首先是操作Activity,只是简单将Fragment、ViewPager和TabLayout绑定起来:

public class ViewModelActivty2 extends AppCompatActivity {
    TabLayout mytab;
    ViewPager mViewPager;
    List<String> mTitle;
    List<Fragment> mFragment;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewmodel2);
        mytab = (TabLayout) findViewById(R.id.mytab);
        mViewPager = (ViewPager) findViewById(R.id.mViewPager);

        mTitle = new ArrayList<>();
        mTitle.add("ONE");
        mTitle.add("TWO");


        mFragment = new ArrayList<>();
        mFragment.add(new FragmentOne());
        mFragment.add(new FragmentTwo());


        mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return mFragment.get(position);
            }

            @Override
            public int getCount() {
                return mFragment.size();
            }

            @Override
            public CharSequence getPageTitle(int position) {
                return mTitle.get(position);
            }
        });

        mytab.setupWithViewPager(mViewPager);


    }
}

接下来是FragmentOne的操作代码,这里的话是点击了按钮进行数据更新操作,然后调用LiveData的setValue()进行数据更新。

public class FragmentOne extends Fragment implements View.OnClickListener{
    private SharedViewModel model;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one,container,false);
        Button btn = view.findViewById(R.id.btn);
        btn.setOnClickListener(this);
        return view;
    }

    @Override
    public void onClick(View view) {
        Person person = new Person();
        person.setName("李大嘴");
        person.setSex("男");
        model.setPerson(person);
    }
}

最后是FragmentTwo的操作页面代码,这里的话主要操作是对LiveData的数据更新进行观察监听,从而更新UI:

public class FragmentTwo extends Fragment {
    SharedViewModel model;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_two,container,false);
        final TextView tv = view.findViewById(R.id.tv);
        model.getPerson().observe(this, new Observer<Person>() {
            @Override
            public void onChanged(@Nullable Person person) {
                tv.setText("修改的值: 名字"+person.getName()+"  性别:"+person.getSex());
            }
        });
        return view;
    }
}

到这里,流程结束。我们看一下运行效果。

Android Jetpack 之 ViewModel

请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。

此方法具有以下优势:
1、Activity 不需要执行任何操作,也不需要对此通信有任何了解。
2、除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
3、每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

好了。今天对ViewModel的介绍就写到这里。共勉