Jetpack学习之ViewModel

ViewModel

如果系统销毁或重新创建Activity或者fragment,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用的某个 Activity 中可能包含用户列表。因配置更改(如旋转屏幕,分辨率改变等)而重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。诸如activityfragment之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。

使用ViewModel可以做到以生命周期形式管理界面数据。将视图和数据分离。架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 activity 或 fragment 实例使用。实现数据共享。

不使用ViewModel

实现一个小功能,点击按钮,textview的数字加1

public class ViewModelStudy extends AppCompatActivity {
    private TextView tv;
    private int i = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model_study);
        tv = findViewById(R.id.tv_test);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                tv.setText(Integer.toString(i));
                i++;
            }
        });
    }
}

但是如果在这过程中屏幕发生旋转,那么显示的值会变为最开始的,而不是和旋转之前的一样。因为屏幕发生了旋转,所以activity重新经历了onCreat的生命周期。不使用ViewModel的话,界面数据不会保存。ViewModel的生命周期如下。可以看到ViewModel的生命周期比activity的时间更长。ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider LifecycleViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。取决于Lifecycle

使用ViewModel

首先继承ViewModel,数据成员是需要保存的界面数据。

// TestViewModel .java
public class TestViewModel extends ViewModel {
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    private int num = 0;
}

//ViewModelStudy.java
public class ViewModelStudy extends AppCompatActivity {
    private TextView tv;
    private TestViewModel testViewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model_study);
        tv = findViewById(R.id.tv_test);
        testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int num = testViewModel.getNum();
                tv.setText(Integer.toString(num));
                testViewModel.setNum(++num);
            }
        });
    }
}

上面这种方式可以实现界面数据的保存,但是旋转之后还是会变为最开始的样子。按下按钮之后数字是旋转前的数字加1,实现了数据的保存。为了严格的实现旋转后数字不变,那么需要使用Livedata,这也是ViewModel一般结合Livedata使用的原因。LiveData的优势,数据始终保持最新状态如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。适当的配置更改,如果由于配置更改(如设备旋转)而重新创建了 activityfragment,它会立即接收最新的可用数据。

public class TestViewModel extends ViewModel {
    private MutableLiveData<Integer> num;

    public MutableLiveData<Integer> getNum() {
        if (num == null)
            num = new MutableLiveData<Integer>(0);
        return num;
    }
    public void setNum(Integer num) {
        this.num.setValue(num);
    }
}

public class ViewModelStudy extends AppCompatActivity {
    private TextView tv;
    private TestViewModel testViewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model_study);
        tv = findViewById(R.id.tv_test);
        testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
        testViewModel.getNum().observe(this,
                new Observer<Integer>() {
                    @Override
                    public void onChanged(Integer integer) {
                        tv.setText(integer.toString());
                    }
                }
        );
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Integer num = testViewModel.getNum().getValue();
                testViewModel.setNum(++num);
            }
        });
    }
}

使用ViewModel进行Fragment之间的通信

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的现象。一般情况这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。可以使用 ViewModel 对象解决这一常见的难点。这两个Fragment可以使用其 Activity 范围共享 ViewModel 来处理此类通信。

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Integer> selected = new MutableLiveData<Integer>();

    public void select(Integer item) {
        selected.setValue(item);
    }

    public LiveData<Integer> getSelected() {
        return selected;
    }
}

public class ListFragment extends Fragment {
    private SharedViewModel model;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)
        //因为这里指定为requireActivity()
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity),
        //因为这里指定为requireActivity()
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), item -> {
           // Update the UI.
        });
    }
}

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

通信实例

// LeftFragment.java
public class LeftFragment extends Fragment {

    private TestViewModel model;
    private TextView tv;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_left, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        model = new ViewModelProvider(requireActivity()).get(TestViewModel.class);
        tv = view.findViewById(R.id.tv_test);
        view.findViewById(R.id.addone).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Integer num = model.getNum().getValue();
                model.setNum(++num);
            }
        });

        model.getNum().observe(getViewLifecycleOwner(), new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                tv.setText(integer.toString());
            }
        });
    }
}
//RightFragment.java
public class RightFragment extends Fragment {
    private TextView tv;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_right, container, false);
    }

    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        TestViewModel model = new ViewModelProvider(requireActivity()).get(TestViewModel.class);
        tv = view.findViewById(R.id.tv_test);
        view.findViewById(R.id.addone).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Integer num = model.getNum().getValue();
                model.setNum(++num);
            }
        });
        model.getNum().observe(getViewLifecycleOwner(), new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                tv.setText(integer.toString());
            }
        });
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值