MVVM

Todo-mvvm-databinding
  • Model: 内存,本地,网络数据的实现与获取
  • View: 对应Activity,Fragment,负责View的绘制,与用户交互,在xml中编写databinding对model层数据的引用绑定,ViewModelHolder<VH>作为非UI的Fragment持有ViewModel,并绑定到Activity的生命周期
  • ViewModel: databinding框架绑定ViewModel,ViewModel持有View中引用的变量observable fields,以及数据管理类TasksRepository,数据或属性发生变化时,databinding自动更新UI
  • 优缺点

    • 低耦合,ViewModel的逻辑代码可重用性,解决MVP中,View,Presenter相互持有问题,响应界面操作无需由View传递,数据变换也无需Presenter调用View实现,开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。可测试,界面素来是比较难于测试的,而现在测试可以针对ViewModel来写
    • ViewModel存在Model的依赖,databinding不方便调试
      代码分析
      TasksActivity,绑定TasksFragment以及ViewModelHolder(持有ViewModel的非UI的Fragment,绑定到Activity的生命周期)
    • 获取TasksViewModel,并绑定到Activity

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      private TasksViewModel findOrCreateViewModel() {
      @SuppressWarnings("unchecked")
      ViewModelHolder<TasksViewModel> retainedViewModel =
      (ViewModelHolder<TasksViewModel>) getSupportFragmentManager()
      .findFragmentByTag(TASKS_VIEWMODEL_TAG);

      if (retainedViewModel != null && retainedViewModel.getViewmodel() != null) {
      // If the model was retained, return it.
      return retainedViewModel.getViewmodel();
      } else {
      // There is no ViewModel yet, create it.
      TasksViewModel viewModel = new TasksViewModel(
      Injection.provideTasksRepository(getApplicationContext()),
      getApplicationContext());
      // and bind it to this Activity's lifecycle using the Fragment Manager.
      ActivityUtils.addFragmentToActivity(
      getSupportFragmentManager(),
      ViewModelHolder.createContainer(viewModel),
      TASKS_VIEWMODEL_TAG);
      return viewModel;
      }
      }
    • 绑定Fragment和View,设置Activity的跳转

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
       public interface TasksNavigator {

      void addNewTask();
      }

      mViewModel = findOrCreateViewModel();
      mViewModel.setNavigator(this);

      // Link View and ViewModel
      tasksFragment.setViewModel(mViewModel);

      TasksViewModel
      void setNavigator(TasksNavigator navigator) {
      mNavigator = navigator;
      }
    • onDestory()回收设置的跳转接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
       @Override
      protected void onDestroy() {
      mViewModel.onActivityDestroyed();
      super.onDestroy();
      }

      TasksViewModel
      void onActivityDestroyed() {
      // Clear references to avoid potential memory leaks.
      mNavigator = null;
      }
    • TasksFragment,持有TasksViewModel,TasksFragBinding,TasksAdapter中持有TaskItemViewModel

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      @Override
      public void onResume() {
      super.onResume();
      mTasksViewModel.start();
      }

      @Nullable
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
      mTasksFragBinding = TasksFragBinding.inflate(inflater, container, false);

      mTasksFragBinding.setView(this);

      mTasksFragBinding.setViewmodel(mTasksViewModel);

      setHasOptionsMenu(true);

      View root = mTasksFragBinding.getRoot();

      return root;
      }
    • 设置SnackBar text属性的监听回调,属性改变时显示SnackBar,在onDestory()中移除回调

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
       private void setupSnackbar() {
      mSnackbarCallback = new Observable.OnPropertyChangedCallback() {
      @Override
      public void onPropertyChanged(Observable observable, int i) {
      SnackbarUtils.showSnackbar(getView(), mTasksViewModel.getSnackbarText());
      }
      };
      mTasksViewModel.snackbarText.addOnPropertyChangedCallback(mSnackbarCallback);
      }

      @Override
      public void onDestroy() {
      mListAdapter.onDestroy();
      if (mSnackbarCallback != null) {
      mTasksViewModel.snackbarText.removeOnPropertyChangedCallback(mSnackbarCallback);
      }
      super.onDestroy();
      }
    • 单个任务的抽象类TaskViewModel,暴露Task的观察者,设置标题,描述,Task,以及SnackBarText

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
       public abstract class TaskViewModel extends BaseObservable
      implements TasksDataSource.GetTaskCallback {

      public final ObservableField<String> snackbarText = new ObservableField<>();

      public final ObservableField<String> title = new ObservableField<>();

      public final ObservableField<String> description = new ObservableField<>();

      private final ObservableField<Task> mTaskObservable = new ObservableField<>();

      private final TasksRepository mTasksRepository;

      private final Context mContext;

      private boolean mIsDataLoading;

      public TaskViewModel(Context context, TasksRepository tasksRepository) {
      mContext = context.getApplicationContext(); // Force use of Application Context.
      mTasksRepository = tasksRepository;

      // Exposed observables depend on the mTaskObservable observable:
      mTaskObservable.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
      @Override
      public void onPropertyChanged(Observable observable, int i) {
      Task task = mTaskObservable.get();
      if (task != null) {
      title.set(task.getTitle());
      description.set(task.getDescription());
      } else {
      title.set(mContext.getString(R.string.no_data));
      description.set(mContext.getString(R.string.no_data_description));
      }
      }
      });
      }

      public void start(String taskId) {
      if (taskId != null) {
      mIsDataLoading = true;
      mTasksRepository.getTask(taskId, this);
      }
      }

      public void setTask(Task task) {
      mTaskObservable.set(task);
      }

      // "completed" is two-way bound, so in order to intercept the new value, use a @Bindable
      // annotation and process it in the setter.
      @Bindable
      public boolean getCompleted() {
      Task task = mTaskObservable.get();
      return task != null && task.isCompleted();
      }

      public void setCompleted(boolean completed) {
      if (mIsDataLoading) {
      return;
      }
      Task task = mTaskObservable.get();

      // Notify repository and user
      if (completed) {
      mTasksRepository.completeTask(task);
      snackbarText.set(mContext.getResources().getString(R.string.task_marked_complete));
      } else {
      mTasksRepository.activateTask(task);
      snackbarText.set(mContext.getResources().getString(R.string.task_marked_active));
      }
      }

      @Bindable
      public boolean isDataAvailable() {
      return mTaskObservable.get() != null;
      }

      @Bindable
      public boolean isDataLoading() {
      return mIsDataLoading;
      }

      // This could be an observable, but we save a call to Task.getTitleForList() if not needed.
      @Bindable
      public String getTitleForList() {
      if (mTaskObservable.get() == null) {
      return "No data";
      }
      return mTaskObservable.get().getTitleForList();
      }

      @Override
      public void onTaskLoaded(Task task) {
      mTaskObservable.set(task);
      mIsDataLoading = false;
      notifyChange(); // For the @Bindable properties
      }

      @Override
      public void onDataNotAvailable() {
      mTaskObservable.set(null);
      mIsDataLoading = false;
      }

      public void deleteTask() {
      if (mTaskObservable.get() != null) {
      mTasksRepository.deleteTask(mTaskObservable.get().getId());
      }
      }

      public void onRefresh() {
      if (mTaskObservable.get() != null) {
      start(mTaskObservable.get().getId());
      }
      }

      public String getSnackbarText() {
      return snackbarText.get();
      }

      @Nullable
      protected String getTaskId() {
      return mTaskObservable.get().getId();
      }
      }
    • TasksItemViewModel,持有Navigator的弱引用,在task_item.xml中,绑定点击事件android:onClick="@{() -> viewmodel.taskClicked()}",以及Task激活状态android:checked="@={viewmodel.completed}",标题android:text="@{viewmodel.titleForList}"

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
       public class TaskItemViewModel extends TaskViewModel {

      // This navigator is s wrapped in a WeakReference to avoid leaks because it has references to an
      // activity. There's no straightforward way to clear it for each item in a list adapter.
      @Nullable
      private WeakReference<TaskItemNavigator> mNavigator;

      public TaskItemViewModel(Context context, TasksRepository tasksRepository) {
      super(context, tasksRepository);
      }

      public void setNavigator(TaskItemNavigator navigator) {
      mNavigator = new WeakReference<>(navigator);
      }

      /**
      * Called by the Data Binding library when the row is clicked.
      */
      public void taskClicked() {
      String taskId = getTaskId();
      if (taskId == null) {
      // Click happened before task was loaded, no-op.
      return;
      }
      if (mNavigator != null && mNavigator.get() != null) {
      mNavigator.get().openTaskDetails(taskId);
      }
      }
      }
    • TasksViewModel,主要是任务列表的数据加载,UI更新

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      public class TasksViewModel extends BaseObservable {

      // These observable fields will update Views automatically
      public final ObservableList<Task> items = new ObservableArrayList<>();

      public final ObservableBoolean dataLoading = new ObservableBoolean(false);

      public final ObservableField<String> currentFilteringLabel = new ObservableField<>();

      public final ObservableField<String> noTasksLabel = new ObservableField<>();

      public final ObservableField<Drawable> noTaskIconRes = new ObservableField<>();

      public final ObservableBoolean tasksAddViewVisible = new ObservableBoolean();

      final ObservableField<String> snackbarText = new ObservableField<>();

      private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS;

      private final TasksRepository mTasksRepository;

      private final ObservableBoolean mIsDataLoadingError = new ObservableBoolean(false);

      private Context mContext; // To avoid leaks, this must be an Application Context.

      private TasksNavigator mNavigator;

      public TasksViewModel(
      TasksRepository repository,
      Context context) {
      mContext = context.getApplicationContext(); // Force use of Application Context.
      mTasksRepository = repository;

      // Set initial state
      setFiltering(TasksFilterType.ALL_TASKS);
      }
    • tasks_frag.xml中绑定TasksFragment,TasksViewModel

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <data>

      <import type="android.view.View" />

      <variable
      name="view"
      type="com.example.android.architecture.blueprints.todoapp.tasks.TasksFragment" />

      <variable
      name="viewmodel"
      type="com.example.android.architecture.blueprints.todoapp.tasks.TasksViewModel" />

      </data>
    • 自定义设置可滑动子View

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      ublic class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout {

      private View mScrollUpChild;

      public ScrollChildSwipeRefreshLayout(Context context) {
      super(context);
      }

      public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
      }

      @Override
      public boolean canChildScrollUp() {
      if (mScrollUpChild != null) {
      return mScrollUpChild.canScrollVertically(-1);
      // return ViewCompat.canScrollVertically(mScrollUpChild, -1);
      }
      return super.canChildScrollUp();
      }

      public void setScrollUpChild(View view) {
      mScrollUpChild = view;
      }
      }
    • ScrollChildSwipeRefreshLayout刷新时加载方法android:onRefresh="@{viewmodel}",自定义setter

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class SwipeRefreshLayoutDataBinding {

      @BindingAdapter("android:onRefresh")
      public static void setSwipeRefreshLayoutOnRefreshListener(ScrollChildSwipeRefreshLayout view,
      final TasksViewModel viewModel) {
      view.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
      @Override
      public void onRefresh() {
      viewModel.loadTasks(true);
      }
      });
      }
      }
    • app:items="@{viewmodel.items}"替换ListView的items

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class TasksListBindings {
      @SuppressWarnings("unchecked")
      @BindingAdapter("app:items")
      public static void setItems(ListView listView, List<Task> items) {
      TasksFragment.TasksAdapter adapter = (TasksFragment.TasksAdapter) listView.getAdapter();
      if (adapter != null)
      {
      adapter.replaceData(items);
      }
      }
      }
    • TasksAapter,TasksItemBinding绑定ViewModel,ListView优化策略达到重复利用Binding,在item的snackbarText发生改变时,改变TasksViewModel的snackbarText,然后显示SnackBar的提示信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      public static class TasksAdapter extends BaseAdapter {

      @Nullable private TaskItemNavigator mTaskItemNavigator;

      private final TasksViewModel mTasksViewModel;

      private List<Task> mTasks;

      private TasksRepository mTasksRepository;

      public TasksAdapter(List<Task> tasks, TasksActivity taskItemNavigator,
      TasksRepository tasksRepository,
      TasksViewModel tasksViewModel) {
      mTaskItemNavigator = taskItemNavigator;
      mTasksRepository = tasksRepository;
      mTasksViewModel = tasksViewModel;
      setList(tasks);

      }

      public void onDestroy() {
      mTaskItemNavigator = null;
      }

      public void replaceData(List<Task> tasks) {
      setList(tasks);
      }

      @Override
      public int getCount() {
      return mTasks != null ? mTasks.size() : 0;
      }

      @Override
      public Task getItem(int i) {
      return mTasks.get(i);
      }

      @Override
      public long getItemId(int i) {
      return i;
      }

      @Override
      public View getView(int i, View view, ViewGroup viewGroup) {
      Task task = getItem(i);
      TaskItemBinding binding;
      if (view == null) {
      // Inflate
      LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());

      // Create the binding
      binding = TaskItemBinding.inflate(inflater, viewGroup, false);
      } else {
      // Recycling view
      binding = DataBindingUtil.getBinding(view);
      }

      final TaskItemViewModel viewmodel = new TaskItemViewModel(
      viewGroup.getContext().getApplicationContext(),
      mTasksRepository
      );

      viewmodel.setNavigator(mTaskItemNavigator);

      binding.setViewmodel(viewmodel);
      // To save on PropertyChangedCallbacks, wire the item's snackbar text observable to the
      // fragment's.
      viewmodel.snackbarText.addOnPropertyChangedCallback(
      new Observable.OnPropertyChangedCallback() {
      @Override
      public void onPropertyChanged(Observable observable, int i) {
      mTasksViewModel.snackbarText.set(viewmodel.getSnackbarText());
      }
      });
      viewmodel.setTask(task);

      return binding.getRoot();
      }


      private void setList(List<Task> tasks) {
      mTasks = tasks;
      notifyDataSetChanged();
      }
      }
willkernel wechat
关注微信公众号
帅哥美女们,请赐予我力量吧!