Android Jetpack 之 ViewModel

简介:

官方解释: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());
        }

    }
}

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

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

我们发现我们刚才操作的数据更新没有改变,而是变成原来的默认数据。这里就出现了我们上面所说的第一种情况,而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());
        }

    }

}

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

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

接下来我们看下它的第二个用途,共享数据,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;
    }
}

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值