项目的代码位于 github 上
项目中采用了MVVM方式进行开发,与之前的MVP项目模式相比,直接去掉了MVP模式中的Presenter,数据加载的操作直接用ViewModel中开始. 在获取到了数据之后,通过databiding 来完成数据的展示。
在to-do中,有几个部分,下面是主界面的分析内容。
在TasksViewModel 这个类当中,封装了数据所有的操作,包括数据的加载,判空(isEmpty),清除, 添加过滤条件。
同时应该注意到TasksViewModel 继承了 BaseObservable 这个对象,BaseObservable 最终实现了Observable 接口, 这个接口提供了让数据绑定的UI接受数据变化的通知。
同时 BaseObservable 还有其他的类型的子类,例如 ObservableField 等。
在 TasksViewModel 我们也可以找到 BaseObservable 类型的属性,通过这些属性,可以监听到具体属性值 的变化。
除了属性值之外,还可以将方法转可以监听的对象,做法就是给方法加上 @Bindable 注解。
下面是这个注解的说明
The Bindable annotation should be applied to any getter accessor method of an
{@link Observable} class. Bindable will generate a field in the BR class to identify
the field that has changed.
它用来修饰任意一个get method,被这个注解修饰的方法会在 BR.java这个类中生成对应的属性,例如如果定义了 String getName()方法,那么就会在BR中添加一个name的常量字段。当数据发生变化我们需要通过getName来获取最新的数据的时候,调用 BaseObserverable 的 notifyPropertyChanged(BR.name),之后getName将再次被调用。
同时也可以看到这个被 @Bindable 注解修饰的方法,可以在 布局文件中直接通过 xxx.name 的方式来访问。
上面说到数据加载操作也是在TasksViewModel中完成的,在loadTasks方法中 调用了 mTasksRepository 的 getTasks, 然后在callback中将数据 添加到了 items当中去,
item 是一个 ObservableList
public final ObservableList<Task> items = new ObservableArrayList<>();
当调用了 items.addAll(tasksToShow); 方法之后,数据就被绑定到界面上去了。
下面跟踪一下,数据绑定是如何实现的。
在TasksViewModel当中,有一个 ObservableBoolean类型的成员变量dataLoading.在这个类中提供了一个set方法,在改变了value的值之后,它调用了notifyChange();
notifyChange 是一个定义在其父类 BaseObservable 中的一个同步的方法, 这个方法当中简单调用了PropertyChangeRegistry 这个类的 notifyCallbacks 方法 ,进入到notifyCallbacks 方法,实际上这个方法又是定义在其父类CallbackRegistry之中,通过CallbackRegistry的说明可知,这个类是用来存放callback和进行各种回调的,但是CallbackRegistry对于回调的处理实际上只是对回调的集合进行了管理,它通过递归的方式来通知集合中所有的回调,这里之所以采用递归的方式,文档之中给出的说明是避免在堆上进行临时状态的分配(但是采用递归需要每次都在栈上分配内存,并且堆上同样会有内存的分配,这里还不清楚),既然CallbackRegistry只是集合回调的管理,那么真正的回调应该如何处理实际上 是通过其内部类 NotifierCallback 来完成。
/**
* Class used to notify events from CallbackRegistry.
*
* @param <C> The callback type.
* @param <T> The notification sender type. Typically this is the containing class.
* @param <A> An opaque argument to pass to the notifier
*/
public abstract static class NotifierCallback<C, T, A> {
/**
* Called by CallbackRegistry during
* {@link CallbackRegistry#notifyCallbacks(Object, int, Object)}} to notify the callback.
*
* @param callback The callback to notify.
* @param sender The opaque sender object.
* @param arg The opaque notification parameter.
* @param arg2 An opaque argument passed in
* {@link CallbackRegistry#notifyCallbacks}
* @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
*/
public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
}
通过实际上在CallbackRegistry 处理回调的时候就是调用了其 onNotifyCallback来处理。
所以在CallbackRegistry 的说明中可以看到 一句话 :A subclass of CallbackRegistry.NotifierCallback must be passed to the constructor to define how notifications should be called
简单说就是由 NotifierCallback的子类来处理notification。
回到 PropertyChangeRegistry 当中看 NOTIFIER_CALLBACK
private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
@Override
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
};
在接收到通知之后,实际上会调用Observable.OnPropertyChangedCallback的 onPropertyChanged 方法。
从上面的分析可以看来,CallbackRegistry 实际上是一个事件分发中心和订阅中,所有的事件回调都是通过CallbackRegistry来处理,但是真正对事件的处理,却是通过自定义的 NotifierCallback来完成的。
在android的databinding中,同样是通过这样一套机制来工作的,在ViewDataBinding中,定义了WeakPropertyListener,通过重写 onPropertyChanged 方法完成对数据的重新绑定。
回到 TasksFragBinding.java 这个类是tasks_frag_xml这个文件生成的。
在其中定义了一个变量, mViewmodel ,因此在这个类中,也为我们生成了一个setViewmodel 方法,在这个方法当中,完成了对数据的mViewmodel 的赋值,然后调用了notifyPropertyChanged 方法,这个方法在前面提到过,定义BaseObservable 当中,所以可以简单的推断 ViewDataBinding 也是继承于BaseObservable。
通过mViewmodel 同样可以推断出,如果我们只是修改了 mViewmodel 的内部属性是无法导致试图被刷新的(已经验证),所以在TaskViewModel 这个类中,我们可以看到,在定义了 private final ObservableField
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 class Task {
public final ObservableField<String> title = new ObservableField();
public final ObservableField<String> content = new ObservableField();
public Task(String title, String content) {
this.title.set(title);
this.content.set(content);
}
public void setContent(String content) {
this.content.set(content);
}
}
对应的布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="task"
type="com.devtom.databindingtest.Task"/>
</data>
<RelativeLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.devtom.databindingtest.MainActivity">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@{task.title}" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@{task.content}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change"
android:onClick="change"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
</layout>
Activity 代码如下
public class MainActivity extends AppCompatActivity {
private Task task;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View view = this.findViewById(R.id.activity_main);
ActivityMainBinding mainBinding = DataBindingUtil.bind(view);
task = new Task("this is title","kai gong da ji");
mainBinding.setTask(task);
}
public void change(View view) {
task.setContent("this is chinese new year");
}
}
当手动的去改变task的某一个属性的时候,也会去刷新对应的试图。
这里需要注意的是Task其中的两个成员变量可以定义为public的也可以定义为private 然后给他添加对应的private 方法。