Android MVVM框架的认识和使用

匿名 (未验证) 提交于 2019-12-03 00:39:02

关于这个MVVM,现在公司的框架就是MVVM,刚开始感觉MVVM真的麻烦,真的不爽,但是真正用起来的时候,感觉比MVP好用多了,好用的不是点半点。= - =。嗯,喜新厌旧~。

关于MVVM的理解如下:

Model :负责数据实现和逻辑处理,类似MVP。 View : 对应于Activity和XML,负责View的绘制以及与用户交互,类似MVP。 ViewModel : 创建关联,将model和view绑定起来,如此之后,我们model的更改,通过viewmodel反馈给view,从而自动刷新界面。

项目中使用的MVVM框架Github地址:
https://github.com/googlesamples/android-architecture/tree/todo-mvvm-databinding

开发者下载该项目,然后需要复制几个工具类到自己的项目中:
ActivityUtils.java
ViewModelHolder.java

然后我们开启android自带的DataBinding开关,再app下的build.gradle中android节点下添加如下代码:

dataBinding {         enabled = true     }

这样MVVM的环境就算配置好了,接下来就是愉快的玩耍了~

首先还是老样子,UI显示:

hello world

首先我们创建我们的Fragment和ViewModel分别通过如下方法:

    @NonNull     private TestOneFragment findOrCreateViewFragment() {         TestOneFragment tasksFragment =                 (TestOneFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);         if (tasksFragment == null) {             // Create the fragment             tasksFragment = TestOneFragment.newInstance();             ActivityUtils.addFragmentToActivity(                     getSupportFragmentManager(), tasksFragment, R.id.contentFrame);         }         return tasksFragment;     }
    private TestOneViewModel findOrCreateViewModel() {         // In a configuration change we might have a ViewModel present. It's retained using the         // Fragment Manager.         @SuppressWarnings("unchecked")         ViewModelHolder<TestOneViewModel> retainedViewModel =                 (ViewModelHolder<TestOneViewModel>) getSupportFragmentManager()                         .findFragmentByTag(TEST_ONE_VIEW_MODEL_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.             TestOneViewModel viewModel = new TestOneViewModel(getApplicationContext());             // and bind it to this Activity's lifecycle using the Fragment Manager.             ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), ViewModelHolder.createContainer(viewModel),                     TEST_ONE_VIEW_MODEL_TAG);             return viewModel;         }     }

因为数据源改变了,XML的值自动改变,说明viewModel控制着XML,那就需要将XML和viewModel链接起来,同理我们有时候还需要获取代码层UI也就是Fragment的数据或者方法,此时也可以将XML和Fragment连接起来。

怎样连接呢?首先需要在XML声明ViewModel和Fragment变量,XML(test_one_fragment)代码如下:

<?xml version="1.0" encoding="utf-8"?> <layout>     <data>         <variable             name="view"             type="com.example.liumengqiang.mvvmoneproject.TestOneFragment"/>          <variable             name="viewModel"             type="com.example.liumengqiang.mvvmoneproject.TestOneViewModel"/>      </data>     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"         android:layout_width="match_parent" android:layout_height="match_parent">         <TextView             android:layout_width="match_parent"             android:layout_height="match_parent"             android:gravity="center"             android:textSize="20sp"             android:textColor="@color/colorAccent"/>     </LinearLayout> </layout>

然后在Fragment中,我们可以获取到自己创建的ViewModel和this(也就是Fragment本身啦)。这个ViewModel是从Activity中传递到Fragment中的。然后Fragment的onCreateView方法代码如下:

   @Nullable     @Override     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,             @Nullable Bundle savedInstanceState) {         View view = inflater.inflate(R.layout.test_one_fragment, null, false);          TestOneFragmentBinding bind = TestOneFragmentBinding.bind(view);         //给XML设置View和ViewModel.         bind.setView(this);         bind.setViewModel(viewModel);          viewModel.onStart();         //TODO 获取网络数据          return view;     }      public void setViewModel(TestOneViewModel viewModel) {         this.viewModel = viewModel;     }

此时在看ViewModel,由于我们要控制XML,所以需要继承自BaseObservable。
代码如下:

public class TestOneViewModel  extends BaseObservable{      private Context context;      public TestOneViewModel(Context context) {         this.context = context;     }      public void onStart() {      }      /**      * 获取数据从服务端      */     public void getDataFromServer() {          //TODO 获取到数据之后,在设置textObservable的值,然后通过mNagivator回调给UI层,做进一步需求操作      } }

此时效果图如下:


  • string
  • int
  • boolean
  • 引用数据类型
  • 图片设置

String的使用

首先开发者要在自己创建的ViewModel中声明:观察者:ObservableField, 由于想表示String类型,所以泛型时String。此时的被观察者就是我们数据源:String。
在ViewModel中声明如下:

    //String 类型     public static ObservableField<String> textObservable = new ObservableField<>();

切记:必须是public 和 static;或者是public 和final,但是是 public static final ,这就牵涉到了 static 和final的区别了。

然后开发者可以在ViewModel中改变textObservable的值,如下:

     public void onStart() {         textObservable.set("hello MVVM");     }

此时效果图如下:


在此需要说一下,XML中的 “@{}”是该框架定义的,也算是格式吧,就是这样定义的。

int的使用
关于Int的使用跟String的用法一样的,只不过将泛型改变成Integer,或者将ObserVableField改成ObserVableInt。用法是一样的,只不过后者不需要添加泛型,因为它已经是一个表示Integer的观察者了。

需要注意的是XML中的TextView不能直接使用Integer类型,需要转化成String类型,框架中的XML可以这样定义“@{~~ + Integer}”
代码如下:

        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:textSize="20sp"             android:textColor="@color/colorAccent"             android:text="@{~~ + viewModel.textObservable}"/>

boolean类型
关于Boolean类型,最常见的需求就是,给定一个Boolean,然后根据布尔值,View显示或者不显示。
首先在ViewModel中定义ObservableBoolean,代码如下:

public static ObservableBoolean observableBoolean = new ObservableBoolean();

然后XML代码如下:

        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:textSize="20sp"             android:visibility="@{viewModel.observableBoolean ? View.VISIBLE : View.GONE}"/>

true显示,false不显示,从代码可以看出,我们使用了View.Visible,所以需要在XML中导入View包,所以在XML顶部添加包:

   <data>          <!--导入View包-->         <import type="android.view.View"/>          <variable             name="view"             type="com.example.liumengqiang.mvvmoneproject.TestOneFragment"/>          <variable             name="viewModel"             type="com.example.liumengqiang.mvvmoneproject.TestOneViewModel"/>      </data>

这样开发者就可以在ViewModel中随意设置observableBoolean的值了。

引用数据类型
引用数据类型的话比较常见,也就是我们从服务器获取到的数据,塞进XML中,首先,需要创建model类,

 public class TestOneModel {     private String title;      private String content;      public TestOneModel(String title, String content) {         this.title = title;         this.content = content;     }      public String getTitle() {         return title;     }      public void setTitle(String title) {         this.title = title;     }      public String getContent() {         return content;     }      public void setContent(String content) {         this.content = content;     } } 

被观察者有了,接下来就是观察者了,开发者需要在ViewModel中创建一个观察者,代码如下:

    //引用数据类型     public static ObservableField<TestOneModel> testOneObservable = new ObservableField<>();

然后给改变被观察者:

    /**      * 获取数据从服务端      */     public void getDataFromServer() {         //TODO 获取到数据之后,在设置textObservable的值,然后通过mNagivator回调给UI层,做进一步需求操作         //假设已经从服务器获取到了数据         TestOneModel testOneModel = new TestOneModel("title", "content");         testOneObservable.set(testOneModel);     }

XML的代码也就是下面的了:

        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_marginTop="10dp"             android:textSize="20sp"             android:textColor="@color/colorAccent"             android:text="@{viewModel.testOneObservable.get().getTitle()}"/>          <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:layout_marginTop="10dp"             android:textSize="20sp"             android:textColor="@color/colorAccent"             android:text="@{viewModel.testOneObservable.get().getContent()}"/>

XML中,testOneObservable.get()的意思是获取到Observable的被观察者,也就是TestOneModel对象。

效果图如下:

图片设置

一般来说实际情况中,图片路径都是在服务器返回的model 中,所以我们在TestOneModel中添加一个成员变量:urlString, 然后赋值:http://img.zcool.cn/community/018d4e554967920000019ae9df1533.jpg@900w_1l_2o_100sh.jpg ,PS:这个路径是是网上找的

然后我们在XML添加如下:

        <ImageView             android:layout_width="50dp"             android:layout_height="50dp"             android:layout_marginTop="10dp"             android:scaleType="fitXY"             app:image_view="@{viewModel.testOneObservable.get().getUrlString()}"/>

注意:从代码中可以看出使用的不是src设置,而是外部属性image_view,这个image_view是随意命名的,然后我们新建一个DataBinding类,声明并实现设置图片。
代码如下:

public class TestOneDateBinding {      @BindingAdapter("image_view")     public static void setImageView(ImageView imageView, String urlString) {         Glide.with(imageView.getContext()).load(urlString).into(imageView);     } }

效果图如下:

注意:这里BindingAdapter声明的名称必须和XML中图片的外部属性必须一致,这里也就是命名成了:image_view。接下来看下方法,这个方法里有两个参数,分别是Imageview和String,这两个是固定的,因为我们是给ImageView设置数据, 然后数据的类型是String类型,所以是固定的,比如之后会写到给RecyclerView设置数据,数据源是:List,那么这是的参数就是RecyclerView 和List<泛型>了。至于方法名,也就是根据开发者需求自己定义的了。

至此以上给基本数据类型和引用数据类型设置数据仅仅表示了MVVM的使用,当然,以前的开发者在UI的View层设置数据,还是有效的,使用步骤:
1:先在XML中声明TextView,

        <TextView             android:id="@+id/tow_method_text"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:textSize="20sp"             android:layout_marginTop="10dp"/>

2:在View(此处是Fragment)中,通过TestOneFragmentBinding的对象binding获取到XML中定义的TextView。

        TextView towMethodText = bind.towMethodText;         towMethodText.setText("我是第二种给XML设置数据的方法");

效果图如下:

从代码可以看出,Fragment中binding的成员变量就是XML中定义的id(去掉了中间的 _), 然后获得TextView对象,接下来的操作是不是熟悉了?

关于点击事件,实际上跟接口回调差不多的,
1:开发者创建一个接口类 TestOneNagivator,

public interface TestOneNagivator {     /**      * TODO 添加点击事件或者是回调事件(连接ViewModel和UI层)      */      //点击事件     void onClick(); }

2:在View层Activity 或者Fragment实现这个接口,我选择在Activity中实现了该接口:

    @Override     public void onClick() {         //TODO         Toast.makeText(this, "点我干啥?", Toast.LENGTH_SHORT).show();     }

3: 由于viewModel是控制器,需要该接口的实例,所以在Activity或者Fragment把接口实例传递给ViewModel

testOneViewModel.setNagivator(this);

4:同理在ViewModel接收该接口实例:

    private WeakReference<TestOneNagivator> mNagivator;
    public void setNagivator(TestOneNagivator nagivator) {         this.mNagivator = new WeakReference<>(nagivator);     }

5:然后开发者还需要在viewModel定义点击事件的方法:

    public void onTextClick() {         if(mNagivator != null && mNagivator.get() != null) {             //回调给UI层             mNagivator.get().onClick();         }     }

6:在XML是调用ViewModel中定义的点击方法onTextClick,假如给图片添加点击事件:

        <ImageView             android:layout_width="50dp"             android:layout_height="50dp"             android:layout_marginTop="10dp"             android:scaleType="fitXY"             app:image_view="@{viewModel.testOneObservable.get().getUrlString()}"              android:onClick="@{() -> viewModel.onTextClick()}"/>

此时点击图片就会弹出:点我干啥?
从XML中可以看出,@{() ->} 这个也是固定的,后面是点击事件的方法名,这也是MVVM点击事件的固定格式,这个不用纠结~

MVVM注意事项:

  1. ViewModel中观察者可以使ObservableArrayList等类型,开发者可以多试试其他类型的。
  2. 如果XML中用到了相应的数据,就需要导入相应的包,比如:在XML中使用了Fragment和ViewModel,就导入了对应的包,只不过又设置了一个别名而已:view 和 viewModel。 相同的还有Boolean的使用。

附上Demo地址:
https://download.csdn.net/download/lmq121210/10511778

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!