Android MVVM框架的认识和使用(一)

关于这个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

猜你喜欢

转载自blog.csdn.net/lmq121210/article/details/80872652