美文网首页Android-Data BindingAndroid 历程data binding
DataBinding难点解析之Observable和Bindi

DataBinding难点解析之Observable和Bindi

作者: 工程师milter | 来源:发表于2016-09-27 16:48 被阅读3693次

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

    一、从最简单的Demo开始

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

    • AS中新建一个项目,开启DataBinding:
    app ——> build.gradle:
        android {
              ...
              dataBinding {
                    enabled = true
              }
              ...
         }         
    
    • 项目中用到的数据类User:
    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;
        }
    }
    
    • MainActivity的布局如下:
    <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!

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

    • 让User类成为一个可被观察的Observable

    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类中会发生变化的属性

    虽然现在我们的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实现原理探析》中,这里不重复解释了。

    • 当属性变化时通知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。

    相关文章

      网友评论

      • winky612:你好,想请教个问题,最近公司在探讨MVP MVVM,技术顾问说,任何好的框架,附带着性能的消耗,所以舍弃了MVVM,选择了MVP.我查了下,好像没看到有这么说的 是这样么?求指教
        Burt_154d:@milter 但只扫描一次生成BR后就不需要再进行了,findview是每次使用都去遍历viewGroup。
        https://academy.realm.io/posts/data-binding-android-boyar-mount/
        winky612:@milter 只是因为多扫描了层布局么?
        工程师milter:@winky612 是的,databinding需要对布局多进行一次扫描,性能肯定会下降
      • siegen:对布局中基本类型对象的监控该怎么实现?即某些控件没有用到实体类,只用基本类型完成绑定,这个时候无法用注解来监控。
        siegen:@milter 好的,谢谢
        工程师milter: @siegen 有专门的Field Observable类,官方文档里有说明,我的文章里也有
      • hwhjxjs:不错
      • 工程师milter:答案就是BR中只有一个name值,被VIP user和normal user共用
        C_Sev:好的。
      • 工程师milter:good question,答案是完全没影响,因为数据类在通知databinding框架时,databinding会知道是哪个对象,也就是说二元对(dataobj,BR)是唯一的。两个数据对象完全可以共享一个BR id而不会产生问题
      • ShiroSora:感谢分享。顺便问一个问题,如果有两个数据类(VipUser和NormalUser)有同样的属性(都是name),BR会如何处理?(当然可以避免这种情况发生,比如让这两个User继承User类,或者取不同的属性名)

      本文标题:DataBinding难点解析之Observable和Bindi

      本文链接:https://www.haomeiwen.com/subject/sbmcyttx.html