Android MVVM框架学习总结(二)

Android MVVM框架学习总结

4. 数据对象

任何POJO对象都可以用于数据绑定,但是更改POJO对象,并不会引起UI更新。有三种不同的数据更改通知机制:观察对象,观察字段和观察集合。当其中一个绑定到用户界面的可观察的数据对象,观察到数据对象的属性变化,用户界面将自动更新。
(1)观察对象(Observable Objects)
A class implementing the Observable interface ,将被允许附加一个listener,来监听对象所有属性的改变
Observable interface有一个机制来添加和删除listeners,但需要通知开发者。为了让开发变得更容易,提供一个基类,BaseObservable,它实现创建listener的注册机制。当属性数据改变时,数据类实现者需要响应通知。这是通过在getter上使用一个注解@Bindable,并在setter中进行通知。

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

@Bindable 在编译时将生成一个BR class 。BR类文件将生成在moudle的package下。如果数据类的基类不能被改变,Observable interface的实现类可以使用PropertyChangeRegistry存储和有效地通知listeners。

(2)观察属性(ObservableFields)
如果想做比创建上面的观察对象更少的工作,那么可以使用ObservableField 和它的同级的一些类型: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。它们都是一个包含单一属性的可观察的对象。为避免装箱、拆箱操作,可以在数据类中定义成 public final field …

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

如下,使用get或set来获取和设置属性值:

user.firstName.set("Google");
int age = user.age.get();

(3)观察集合(Observable Collections)
一些应用程序使用更多的动态结构来保存数据。可观察集合允许通过key访问这些数据对象。当key是一个引用类型,如String,就可以用ObservableArrayMap。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在下面的布局中,就可以通过String类型的key来访问map中的数据:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data><TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

当key是一个Integer时,可用ObservableArrayList:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

布局中使用ObservableArrayList:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>

<TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

5. 生成Binding

生成的绑定类,会链接布局的variables。正如前面所讨论的,绑定的名称和包可能是自定义的的。生成的所有绑定类都 extends ViewDataBinding。
(1)创建
如前文所述,由相应的布局文件,而生成了相应的binding class。代码中使用它,需要LayoutInflater,如:

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

如果布局已经inflate了,在某些场景时,可以单独绑定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时binding class 不能提前知道。在这种情况下,可以使用DataBindingUtil类创建绑定:

ViewDataBinding binding = DataBindingUtil.inflate(
        LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

(2)View中使用id
在布局中的每个使用了id的view,都会在binding class中创建出一个同名的public属性。这种绑定机制,比findViewById更快速。

<TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>

在Activity或者Fragment中可以这样引用,binding.firstName

(3)高级绑定
1)动态Variable
有时,特定的绑定类不会为人所知。例如,一个RecyclerView.Adapter操作的任意布局,不知其特定的绑定类。仍需要通过onBindViewHolder(VH, int)来绑定值。
在下面的例子中,RecyclerView中的所有布局,都绑定了一个”item”,有一个BindingHolder#getBinding() 返回ViewDataBinding 类型:

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

2)直接绑定
当一个variable or observable发生了改变,绑定框架会安排在下一帧进行视图的改变。然而,有时希望立即发生改变。可以使用executePendingBindings()来强制执行

3)后台线程
你可以改变你的数据模型在一个后台线程,只要它不是一个集合。数据绑定框架将本地化每个变量和属性,以避免任何并发问题。

6.设置属性

当view使用了绑定表达式,只要绑定值发生变化,生成的绑定类必须调用相应的setter方法。定制数据绑定框架的方式方法调用设置值。数据绑定框架允许自定义setter方法。
自动Setters
对于一个attribute,数据绑定框架将试图查找对应的setAttribute()。它的namespace并不重要,只关注attribute的name。如,TextView中的属性android:text使用了表达式,那么框架就会查找setText(String)。如果表达式返回的是int,那么将会查找setText(int)。所以,表达式需要返回正确的类型。数据绑定框架,支持创建一个布局元素(View|ViewGroup)中,并不存在的属性。
如下,生成的binding class中,将生成一个setDrawerListener(listener):

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

自定义Setters
一些属性需要自定义绑定逻辑。例如,属性android:paddingLeft没有对应的setter方法,而在view中有一个方法为setPadding(left, top, right, bottom)。可以使用@BindingAdapter来自定义一个关于属性android:paddingLeft的setter。例:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

还可以用Binding adapter来接收多个参数:

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}

对应的xml绑定:

<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

当ImageView中同时使用了属性String imageUrl和Drawlable error,通过Binding adapter,这时的setter就是 loadImage()。

7.转换器

对象转换
当从一个绑定表达式返回一个对象,就会有一个setter被采用。该对象将会被转换成setter中的参数类型。
下面的例子,通过ObservableMaps来保存数据:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

userMap返回一个对象,该对象将自动转换成setText(CharSequence)中的参数类型。可能会有混乱的参数类型,开发人员需要在表达式中显式cast

自定义转换
有时特定类型之间会自动转换。例如,设置背景:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

这里,background的setter的参数类型应该是一个drawable,但color是一个整数。应当有一个转换规则,将int color转换为ColorDrawable。这种转换是通过使用一个@BindingConversion的静态方法来实现的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

注意,转换只发生在setter时期。所以不允许混合类型,如:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

猜你喜欢

转载自blog.csdn.net/u011897062/article/details/79769588