Android-Data BindingAndroid 历程data binding

DataBinding难点解析之Observable和Bindi

2016-09-27  本文已影响3693人  工程师milter

本文深入解析了DataBinding中的Observable模式和BindingAdapter两个知识点,适合有一定的DataBinding基础,想进一步理解掌握它的原理的Android开发者。强烈建议先阅读本人另一篇文章DataBinding实现原理探析,对理解本文帮助很大。
// TODO 测试RecyclerView中使用databinding后,还需要notifyItemChanged吗?

一、从最简单的Demo开始

我知道,一开始就丢出一堆代码给别人看,是一件很无趣的事儿,但这是必不可少的,因为我需要你先了解我所用的Demo,才能基于这个Demo进行探讨。所以,熟悉下这个再简单不过的Demo吧。

app ——> build.gradle:
    android {
          ...
          dataBinding {
                enabled = true
          }
          ...
     }         
public class User {
    private String name ;

    public User(String name) {
        this.name = name ;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
<layout  xmlns:android="http://schemas.android.com/apk/res/android" >
    <data>

        <variable
            name="user"
            type="com.milter.www.databinding_observable.User"/>

    </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"
    android:gravity="center_vertical|center_horizontal"
    tools:context="com.milter.www.databinding_observable.MainActivity">

    <TextView android:id="@+id/username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="35sp"
        android:text="@{user.name}" />
    <Button android:id="@+id/changeUserName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/username"
        android:layout_marginTop="50dp"
        android:text="@string/changeusername"

        />
</RelativeLayout>
</layout>

MainActivity的代码如下:

package com.milter.www.databinding_observable;

import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.milter.www.databinding_observable.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    private  User user ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding mainActivityBinding =  DataBindingUtil.setContentView(this,R.layout.activity_main);

        user = new User("milter");
        mainActivityBinding.setUser(user);

        mainActivityBinding.changeUserName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                user.setName("Not milter");
            }
        });

    }
}

在上面的代码中,我们用参数“milter”创建了一个user对象,然后把它设置给了mainActivityBinding,之后,我们给id为changeUserName的Button添加了一个点击监听器,点击它将会调用user 的setName方法将它的name属性改为“Not milter”

程序运行起来的界面如下所示:

demo.png

够简单吧!

二、点击按钮后,milter会变成 Not miter吗?

请你猜猜上面的问题,无论对错,希望你能够告诉我结果。

正确答案是:NO!
我们肯定要问:WHY?

先来看看milter是怎么显示在id为username的TextView中的。代码mainActivityBinding.setUser(user);执行后,DataBinding框架会进行数据与UI的绑定,id为username的TextView通过android:text="@{user.name}"绑定了user对象的name属性,所以显示了milter。

点击按钮后,user对象的name属性变成了“Not milter”,但是DataBinding框架并不知道这件事儿,所以它不会重新进行数据与UI的绑定,结果就是username中显示的还是milter。

解决问题的关键在于,怎么让user对象告诉DataBinding框架,我的name属性改变了,赶紧更新UI吧!

三、实现点击按钮后milter变 Not milter!

为了达到目标,有三件事要做,我把它称为绑定数据“三步曲”

DataBinding框架提供了一个android.databinding.Observable接口,只要让User类实现这个接口,DataBinding框架就会向我们的user对象注册一个监听器,有了这个监听器,当user对象的属性发生变化后,它就可以通知DataBinding框架,DataBinding框架收到user对象的通知后,就会更新UI数据。

但是,实现Observable接口,相当于让我们自己实现一个观察者模式,还是有点麻烦的,所以DataBinding框架为我们提供了一个BaseObservable类,该类已经实现了Observable接口,所以,我们只要让User类继承它也就自然实现了Observable接口了。
实现了Observable接口的User类如下所示:

import android.databinding.BaseObservable;  //这里不要搞错哦!
public class User extends BaseObservable {
    private String name ;

    public User(String name) {
        this.name = name ;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

虽然现在我们的User类只有一个name属性,但现实中,User类还可以有许多属性,比如sex,age,phoneNumber等。我们希望user对象通知DataBinding框架的时候,能够精确一点,比如告诉DataBinding框架,我的name属性变化了,请更新UI中使用我的name属性的View。这样可以大大减轻DataBinding框架的工作量。

假如user对象仅仅告诉DataBinding框架,我的属性发生了变化,请更新UI吧。那么DataBinding框架将不得不把UI中所有使用user对象属性的View更新一遍,显然,更新那些没有变化的属性纯粹是一种浪费。

确定会发生变化的属性非常简单,就是在相应的getter方法上加上@Bindable注解。在我们的Demo中,要给getName方法上面加上@Bindable注解,如下所示:

@Bindable
 public String getName() {
        return name;
    }

加上这个注解后,DataBinding框架会在BR这个生成类中,为name属性生成一个唯一的标识符,如下所示:

public class BR {
    ...
    public static final int name = 1;
    ...
}

这样,当user对象通知DataBinding框架时,可以用BR.name标识自己的name属性。

你可能会问,为什么不把@Bindable注解加到name field上,像下面这样:

@Bindable
private String name ;

答案在文首推荐的《DataBinding实现原理探析》中,这里不重复解释了。

现在,当user对象的name属性发生变化后,我们就可以通知DataBinding框架了,显然,应该在setName方法中通知DataBinding框架。如下所示:

public void setName(String name) {
        
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

notifyPropertyChanged方法来自BaseObservable类。

完成了我们的“三步曲”,本节的目的就达到了。
点击按钮前后对比如下:

compare.png

四、更加简单的方法

上一部分中,仅仅为了让user在自己的name属性发生变化时通知DataBinding框架,我们要做许多工作,又是继承,又是加注解的,确实有点麻烦!

为此,DataBinding框架给我们提供一个简便方法,那就是使用ObservableField。使用它,我们的User类将变成这样:

public class User{
   
    public final ObservableField<String> name = new ObservableField<>();

    public User(String name) {    
        this.name.set(name);
    }

}

然后,将按钮的点击监听器中的代码变成这样:
user.name.set("Not milter");

这就可以了,效果和之前是一样一样的。
更进一步,如果属性是基本数据类型,DataBinding框架还提供了专门的属性类:

 ObservableBoolean,
 ObservableByte, 
 ObservableChar, 
 ObservableShort, 
 ObservableInt,
 ObservableLong,
 ObservableFloat, 
 ObservableDouble, 
 ObservableParcelable

再进一步,如果属性是集合,DataBinding框架也提供了专门的类:

ObservableArrayMap
ObservableArrayList

使用ObservableField 等ObservableXXX类,可以让我们省去继承BaseObservable、添加注解,通知DataBinding框架等麻烦,但天下没有免费的午餐,代价就是性能会降低,所以只能在少量属性上这样用,如果大量使用,用户体验可能不太好。
好了,关于Observable的部分就到这里了,突然发现文章已经不短了。只好下一节再讲BindingAdapter。

上一篇 下一篇

猜你喜欢

热点阅读