关于这个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注意事项:
- ViewModel中观察者可以使ObservableArrayList等类型,开发者可以多试试其他类型的。
- 如果XML中用到了相应的数据,就需要导入相应的包,比如:在XML中使用了Fragment和ViewModel,就导入了对应的包,只不过又设置了一个别名而已:view 和 viewModel。 相同的还有Boolean的使用。
附上Demo地址:
https://download.csdn.net/download/lmq121210/10511778