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