博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
LiveData与ViewModel结合,看起来挺美!
阅读量:4101 次
发布时间:2019-05-25

本文共 9509 字,大约阅读时间需要 31 分钟。

1.前言

虽说这篇是说LiveData与ViewModel,但是或多或少都有涉及另外一个组件:Lifecycles 。它们连同Room都是在17年谷歌IO大会推出的,当时还是预览版,大致17年底时推出了正式版。到今年的IO大会过后,又增加了许多新成员。

 

 

可以看到27.0.0的v7库有依赖Lifecycles。

 

 

当时Lifecycles有集成进SupportActivity。

 

 

其实一开始我没有太当回事。。。直到27.1.0以后:

 

 

好吧,今天的主角出现了,LiveData与ViewModel。看到这里我觉得是该了解一波了。

顺便看一下截止目前最新的v7:

 

 

发现好多常用的组件分离出了v4包,比如ViewPager、SwipeRefreshLayout,这里就不多说了。

2.优势

LiveData 是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。同时,LiveData 持有界面代码 Lifecycle 的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。

上面的描述介绍了LiveData的优点:不用手动控制生命周期,不用担心内存泄露,数据变化时会收到通知。

ViewModel 将视图的数据和逻辑从具有生命周期特性的实体(如 Activity 和 Fragment)中剥离开来。直到关联的 Activity 或 Fragment 完全销毁时,ViewModel 才会随之消失,也就是说,即使在旋转屏幕导致 Fragment 被重新创建等事件中,视图数据依旧会被保留。ViewModels 不仅消除了常见的生命周期问题,而且可以帮助构建更为模块化、更方便测试的用户界面。

ViewModel的优点也很明显,为Activity 、Fragment存储数据,直到完全销毁。尤其是屏幕旋转的场景,常用的方法都是通过onSaveInstanceState()保存数据,再在onCreate()中恢复,真的是很麻烦。

其次因为ViewModel存储了数据,所以ViewModel可以在当前Activity的Fragment中实现数据共享。

那么LiveData与ViewModel的组合使用可以说是双剑合璧,而Lifecycles贯穿其中。

3.基本使用

这里我们按照官方Demo来简单说明,

3.1 数据储存

/** * A ViewModel used for the {@link ChronoActivity3}. */public class LiveDataTimerViewModel extends ViewModel {    private static final int ONE_SECOND = 1000;    private MutableLiveData
mElapsedTime = new MutableLiveData<>(); private long mInitialTime; public LiveDataTimerViewModel() { mInitialTime = SystemClock.elapsedRealtime(); Timer timer = new Timer(); // Update the elapsed time every second. timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000; // setValue() cannot be called from a background thread so post to main thread. mElapsedTime.postValue(newValue); } }, ONE_SECOND, ONE_SECOND); } public LiveData
getElapsedTime() { return mElapsedTime; }}

LiveDataTimerViewModel很简单,在初始化时启动一个定时任务,每隔一秒通过postValue方法刷新一下数据。

public class ChronoActivity3 extends AppCompatActivity {    private LiveDataTimerViewModel mLiveDataTimerViewModel;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.chrono_activity_3);        mLiveDataTimerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);        subscribe();    }    private void subscribe() {        final Observer
elapsedTimeObserver = new Observer
() { @Override public void onChanged(@Nullable final Long aLong) { String newText = ChronoActivity3.this.getResources().getString( R.string.seconds, aLong); ((TextView) findViewById(R.id.timer_textview)).setText(newText); Log.d("ChronoActivity3", "Updating timer"); } }; // mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver); }}

Activity也很简单,创建一个观察者elapsedTimeObserver。当LiveDataTimerViewModel中数据有变化时,它就会接收到最新的数据。当然你的页面要处于STARTED 或者 RESUMED。除非你使用observeForever来观察数据,有兴趣的可以去查看源码来了解实现原理。

mLiveDataTimerViewModel.getElapsedTime().observeForever(elapsedTimeObserver);

 

3.2 Fragmnet 之间数据共享

public class SeekBarViewModel extends ViewModel {    public MutableLiveData
seekbarValue = new MutableLiveData<>();}

SeekBarViewModel中存储一个Integer类型的数据。

public class Fragment_step5 extends Fragment {    private SeekBar mSeekBar;    private SeekBarViewModel mSeekBarViewModel;    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,                             Bundle savedInstanceState) {        // Inflate the layout for this fragment        View root = inflater.inflate(R.layout.fragment_step5, container, false);        mSeekBar = root.findViewById(R.id.seekBar);        //注意这里是getActivity()        mSeekBarViewModel = ViewModelProviders.of(getActivity()).get(SeekBarViewModel.class);        subscribeSeekBar();        return root;    }    private void subscribeSeekBar() {        // 当SeekBar变化时,更新ViewModel中的数据.        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {            @Override            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                if (fromUser) {                    Log.d("Step5", "Progress changed!");                    mSeekBarViewModel.seekbarValue.setValue(progress);                }            }            ......        });        // 当ViewModel数据变化时,更新SeekBar。        mSeekBarViewModel.seekbarValue.observe(this, new Observer
() { @Override public void onChanged(@Nullable Integer value) { if (value != null) { mSeekBar.setProgress(value); } } }); }}

实现效果:

 

 

 

这个页面是上下各有一个Fragment_step5的Fragment,Fragment中各有一个SeekBar。效果是拖动其中的SeekBar,另一边的SeekBar也会随之一样变化。

4.简化使用

这里我写了一个小小的工具库Saber来处理(好吧,猝不及防的广告。。。),使用注解处理器(Annotation Processor)将繁琐的代码自动生成。

首先创建一个类,使用@LiveData注解标记你要保存的数据。注意这里的参数名称,下面会用到。

public class SeekBar { @LiveData Integer value; }

Build – > Make Project 生成代码如下:

public class SeekBarViewModel extends ViewModel {  private MutableLiveData
mValue; public MutableLiveData
getValue() { if (mValue == null) { mValue = new MutableLiveData<>(); } return mValue; } public Integer getValueValue() { return getValue().getValue(); } public void setValue(Integer mValue) { if (this.mValue == null) { return; } this.mValue.setValue(mValue); } public void postValue(Integer mValue) { if (this.mValue == null) { return; } this.mValue.postValue(mValue); }}

提供了ViewModel的常用操作。setXXX()要在主线程中调用,而postXXX()既可在主线程也可在子线程中调用。一般情况下可以直接使用。比如上面的Fragment例子。简化为:

public class TestFragment extends Fragment {    private SeekBar mSeekBar;    @BindViewModel(isShare = true) //<--标记需要绑定的ViewModel    SeekBarViewModel mSeekBarViewModel;    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {        // Inflate the layout for this fragment        View root = inflater.inflate(R.layout.fragment_test, container, false);        mSeekBar = root.findViewById(R.id.seekBar);        Saber.bind(this); // <--这里绑定ViewModel        subscribeSeekBar();        return root;    }    private void subscribeSeekBar() {        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {            @Override            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                if (fromUser) {                    mSeekBarViewModel.setValue(progress);                }            }            ......        });    }    @OnChange(model = "mSeekBarViewModel") //<--接收变化    void setData(Integer value){ //注意这里使用 @LiveData 标记的参数名        if (value != null) {            mSeekBar.setProgress(value);        }    }}

默认使用@BindViewModel用于数据储存,如果需要Fragment之间数据共享,需要@BindViewModel(isShare = true),当然也要保证传入相同的key值。默认key值是类的规范名称,也就是包名加类名。

 

 

所以一旦需要互通的Fragment类名或包名不一致,就无法数据共享。这时可以指定key值:@BindViewModel(key = "value")

对于第一个例子我们可以这样使用:

public class LiveDataTimerViewModel extends TimerViewModel {// <-- 继承生成的ViewModel    private static final int ONE_SECOND = 1000;    private long mInitialTime;    public LiveDataTimerViewModel() {        mInitialTime = SystemClock.elapsedRealtime();        Timer timer = new Timer();        // Update the elapsed time every second.        timer.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;                // setValue() cannot be called from a background thread so post to main thread.                postTime(newValue); //<--直接使用post方法。            }        }, ONE_SECOND, ONE_SECOND);    }}

Activity如下:

public class ChronoActivity3 extends AppCompatActivity {    private TextView textView;    @BindViewModel    LiveDataTimerViewModel mTimerViewModel;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);               textView = this.findViewById(R.id.tv);        Saber.bind(this); // <-- 绑定    }    @OnChange(model = "mTimerViewModel")    void setData(Long time){        String newText = MainActivity.this.getResources().getString(R.string.seconds, time);        textView.setText(newText);        Log.d("ChronoActivity3 ", "Updating timer");    }}

是不是使用起来更加的简洁了,如果一个页面有多个ViewModel可能效果更加的明显。

5.原理

因为实现大量借鉴了butterknife,所以使用方法与butterknife几乎一模一样。是不是想起了 butterknife 的@BindView 与 @OnClick。

其实原理也不复杂,就是生成一个类来帮我们来获取ViewModel并实现数据的变化监听。如下:

public class MainActivity_Providers implements UnBinder {  private MainActivity target;  @UiThread  public MainActivity_Providers(MainActivity target) {    this.target = target;    init();  }  private void init() {    target.mTimerViewModel = ViewModelProviders.of(target).get(LiveDataTimerViewModel.class);    target.mTimerViewModel.getTime().observe(target, new Observer
() { @Override public void onChanged(Long value) { target.setData(value); } }); } @CallSuper @UiThread public void unbind() { MainActivity target = this.target; if (target == null) { throw new IllegalStateException("Bindings already cleared."); } this.target = null; }}

免费分享Java技术资料,需要的朋友可以在关注后私信我,一键领取

 

转载地址:http://wyusi.baihongyu.com/

你可能感兴趣的文章
(python版)《剑指Offer》JZ01:二维数组中的查找
查看>>
(python版)《剑指Offer》JZ06:旋转数组的最小数字
查看>>
(python版)《剑指Offer》JZ13:调整数组顺序使奇数位于偶数前面
查看>>
(python版)《剑指Offer》JZ28:数组中出现次数超过一半的数字
查看>>
(python版)《剑指Offer》JZ30:连续子数组的最大和
查看>>
(python版)《剑指Offer》JZ02:替换空格
查看>>
JSP/Servlet——MVC设计模式
查看>>
使用JSTL
查看>>
Java 8新特性:Stream API
查看>>
管理用户状态——Cookie与Session
查看>>
最受欢迎的前端框架Bootstrap 入门
查看>>
JavaScript编程简介:DOM、AJAX与Chrome调试器
查看>>
通过Maven管理项目依赖
查看>>
通过Spring Boot三分钟创建Spring Web项目
查看>>
Spring的IoC(依赖注入)原理
查看>>
Java编程基础:static的用法
查看>>
Java编程基础:抽象类和接口
查看>>
Java编程基础:异常处理
查看>>
Spring MVC中使用Thymeleaf模板引擎
查看>>
Spring处理表单提交
查看>>