Data Binding 使用

private ActivityMainBinding mBinding;
protected void onCreate(Bundle savedInstanceState) {
  mBinding = DataBindingUtil.setContentView(this,
                           R.layout.activity_main);
}

public void updateUI(User user) {
  mBinding.setUser(user);
}
<layout>
    <data>
        <variable name="user"
                  type="com.android.example.User"/>
    </data>
    <LinearLayout …>
        <TextView android:text="@{user.name}"/>
        <TextView android:text="@{user.lastName}"/>
        <TextView android:text='@{"" + user.age}'/>
    </LinearLayout>
</layout>

为什么要使用 Data Binding

Android 开发的 Data Binding 库可以使开发者以最小的力气,快速构建丰富的具有响应性的用户体验。通过删除 样板数据驱动的用户界面,说明如何使用 Data Binding 改善您应用程序的开发,使代码更加干净优雅。可以切确地说:只要你在 Android 开发中使用 Data Binding,很快你就会感觉到它所带来到好处。

 

但是它怎么工作的呢?

Data Binding 做的第一件事就是进入并处理您的布局文件,其中的“进入”指的是,在你的程序代码正在被编译的过程中,它会找出布局文件中所有关于它的内容,获取到它所需要的信息,然后删掉它们,删掉它们的原因是如果继续存着视图系统并不认得它们。

第二步骤就是通过语法来解析这些表达式,例如:

<TextView android:visibility="@user.isAdmin ? View.VISIBLE : View.GONE}"/>

它的工作就是从这些文件中把东西解析出来,了解里面有什么。

第三步就是在你代码编译过程中解决相关依赖问题。在这一步中,例如,我们看一下 user.isAdmin,想:“这是在运行时获取到 User 类对象中的一个布尔值。”

 最后一步就是 Data Binding 会自动生成一些你不需要再写的类文件

Data Binding 特别吸引人的地方

Data Binding 使你的工作更加简单。让我们来看一下我们所支持的语言:支持绝大部分的 Java 写法,它允许变量数据访问、方法调用、参数传递、比较、通过索引访问数组,甚至还支持三目运算表达式。 这基本上能够满足你的需求,但也有一些事情无法办到,比如 new,我们真的不想你在布局中的表达式里写 new.

也会自动检查是否为 null,如果你想访问 contact 的 name 变量,而 contact 是 null 的,以前你不得不痛苦地写contact null ? null : contact.friend null ? :,而现在,如果 contact 是 null 的,那么整个表达式就是 null 的,你无需判断也不会出错。

我们还提供了一个 null 的合并运算符号 ??,这是一个三目运算符的简便写法:

contact.lastName ?? contact.name
contact.lastName != null ? contact.lastName : contact.name

它表达的是,如果左边不是 null 的,那么使用左边的值,否者使用右边的值。

我们还可以使用中括号操作符来访问 list 或者 map,如果你有使用 contacts[0],它有可能是一个 list 或者是一个数组,都可以。使用中括号来访问项目,更加容易简洁。

资源内容

我们希望你能在你的表达式中使用资源引用内容,因此你可以在你的表达式中使用资源和字符串格式化方法。

在表达式中:

android:padding="@{isBig ? @dimen/bigPadding : @dimen/smallPadding}"

内联字符串格式:

android:text="@{@string/nameFormat(firstName, lastName)}"

内联复数:

android:text="@{@plurals/banana(bananaCount)}"

事件处理

我们使用 Data Binding 也是支持点击响应的。你可以使用 clicked,同时,另外的一些事件也都支持。支持到 Android 2.3 版本。你还可以做一些偏门的监听器,比如 onTextChanged,TextWatcher 有三个方法,但大部分人都只关心它的 onTextChanged,借助 Data Binding,你可以直接访问任意一个或全部你想访问的:

 

<Button android:onClick="clicked" …/>

<Button android:onClick="@{handlers.clicked}" …/> 

<Button android:onClick="@{isAdult ? handlers.adultClick : handlers.childClick}" …/> 

<Button android:onTextChanged="@{handlers.textChanged}" …/>

可观测性 

首先我们需要创建一个项目,包含一些可以被观测的对象。在这里,我已经继承了个 BaseObservable,然后我们在这个新的类当中加入我们的属性。

public class Item extends BaseObservable {
    private String price;

    @Bindable
    public String getPrice() {
        return this.name;
    }

    public void setPrice(String price) {
        this.price = price;
        notifyPropertyChanged(BR.price);
    }
}

 

我们使用 notifyPropertyChanged 来进行数据改变完成通知,但我们怎么通知一个数据即将改变?我们不得不写一个 @Bindable 注解在 getPrice。这将会自动产生一个 BR.price,这个 BR 很像我们经常使用的 R 类文件,我们通过这些注解会自动生成它。但是,你可能不想让我们入侵你的整个代码体系,所以我们允许你去实现这些可被观测的类。自己实现的示例如下:

public class Item implements Observable {
    private PropertyChangeRegistry callbacks = new …
    …
    @Override
    public void addOnPropertyChangedCallback( 
            OnPropertyChangedCallback callback) {
        callbacks.add(callback);
    }
    @Override
    public void removeOnPropertyChangedCallback( 
            OnPropertyChangedCallback callback) {
        callbacks.remove(callback);
    }
}

我们有一个很方便的类 PropertyChangedRegistry,让你很方便地进行回调和通知。你们中的一些人可能会觉得这是一件很讨厌的事情,他们更多的是想要一个可观测的属性变量。从本质上讲,它们每一个都是一个可观测都对象。你可以很方便地说,accessImage, 它实际上就会去访问对应图片地内容,如果你要访问 price,它就会去访问价格的字符串内容。

关于这些对象的特别之处在于,以前使用 Java 代码,你必须得调用 set 或者 get 方法,但在你的绑定表达式中,你只要写 item.price,我们能够自动知道你需要调用它的 getter 方法,所以当价格发生变化时,它才设定它的值。

public class Item {
    public final ObservableField<Drawable> image =
            new ObservableField<>();
    public final ObservableField<String> price =
            new ObservableField<>();
    public final ObservableInt inventory =
            new ObservableInt();
}

item.price.set("$33.41");

在其他情况下,你可能有更多的“细碎、不确定”的数据,这通常发生在你开发周期的一开始,特别是在原型阶段,可能你会有很多键值数据对,并且你很不想定义这些类,所以你可能会想使用 map 来存储这些数据对。这种情况下,你可以使用一个可观测的 ObservableMap 来放你的项目,然后就可以访问它们了。不幸的是,你只能使用中括号类似访问数组那样访问它们:

ObservableMap<String, Object> item =
        new ObservableArrayMap<>();

item.put("price", "$33.41"); 
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:text='@{item["price"]}'/>

性能

对于性能的最重要的方面是,我们基本上是零反射。一切都发生在编译期。

其次,你还可以得到一些额外的好处。让我们来讨论在一个布局中使用数据绑定,你把一个对象命名为price,然后价格变化了。新的价格来了,通知来了。数据绑定会更新 TextView,TextView 自动进行重新测量。如果你是用手工写这些功能的代码,那你就不太可能写下这样的代码,而是会重新关闭再加载新内容。所以这是一个额外的好处。

关于性能的另一个作用是针对 findById。当你在Android系统上使用代码findById,你的代码findViewById对于其他 View 一次一次不断循环查找。

当你初始化数据绑定时,我们实际上知道在编译时我们对应的 View,所以我们有一个方法来找到我们想要的所有 View。我们只需遍历布局层次一次,就可以收集到所有的 View。这个计算只对于一个布局只发生一次。当第二次我们需要另一个其中的子 View 的时候,不需要再次遍历所有子 View,因为我们已经找到了所有的 View。

性能有时关键就在一些小细节上。你在你的代码中包含一个库,一些行为会改变,但有时也会有一些成本。但有了这些性能上的优化,我认为我们做了它值得了,甚至有时比你写的代码还好。

RecyclerView 和 Data Binding 多种类型视图

<layout>
    <data>
        <variable name="data" type="com.example.Photo"/>
    </data>
    <ImageView android:src="@{data.url}" …/>
</layout>

如果你需要另一种类型的结果,比如说叫“place”,那么你就需要有一个完全不同的布局,另一个XML文件:

<layout>
    <data>
        <variable name="data" type="com.example.Place"/>
    </data>
    <ImageView android:src="@{data.url}" …/>
</layout>

这两个布局文件之间唯一共享的,就是变量名,即所谓的“data”。

不幸的是,在这个基类当中并没有一个 setPlace 方法,但幸运的是我们提供了另外一个 API 叫做 setVariable

public void bindTo(Object obj) {
  mBinding.setVariable(BR.data, obj);
  mBinding.executePendingBindings();
}

你可以提供需要绑定的目标 ID 和对应的整个对象,自动生成的代码会去检查它的类型,并将其赋值。

这个 setVariable 看起来像这样:“如果传进来的 ID 是我知道的,我就将它转型并且赋值。”

boolean setVariable(int id, Object obj) {
  if (id == BR.data) {
    setPhoto((Photo) obj);
    return true;
  }
  return false;
}

对于 onBindonCreate 方法类似以往的写法,比较特别的就是 getItemViewType,我们返回布局文件的 ID 作为这个方法的返回值。RecyclerView 会将它传递给 onCreateViewHolderonCreateViewHolder 将通过 DataBindingUtil 去创建对应的绑定类。这样所有项目都会有它们的布局,你不需要手动去创建这些不同布局类型的对象了。

DataBoundViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
  return DataBoundViewHolder.create(mLayoutInflater, viewGroup, type);
}

void onBindViewHolder(DataBoundViewHolder viewHolder, int position) {
  viewHolder.bindTo(mDataList.get(position));
}

public int getItemViewType(int position) {
  Object item = mItems.get(position);
  if (item instanceof Place) {
    return R.layout.place_layout;
  } else if (item instanceof Photo) {
    return R.layout.photo_layout;
  }
  throw new RuntimeException("invalid obj");
}

当然,如果你是在一个生产应用程序中,你可能会保留做实例检查。你应该有一个基本的类,知道如何返回布局,这是通常的想法。

public class AllBindingAdapter extends RecyclerView.Adapter<AllBindingAdapter.DataBoundViewHolder>{

    private List<Object> allNames;

    public AllBindingAdapter(List<Object> list) {
        this.allNames = list;
    }

    @NonNull
    @Override
    public DataBoundViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return DataBoundViewHolder.create(LayoutInflater.from(parent.getContext()), parent, viewType);
    }

    @Override
    public void onBindViewHolder(@NonNull DataBoundViewHolder holder, int position) {
        holder.bindTo(allNames.get(position));
    }

    @Override
    public int getItemCount() {
        return allNames.size();
    }

    @Override
    public int getItemViewType(int position) {
        Object item = allNames.get(position);
        if (item instanceof BlankBean) {
            return R.layout.item_all_layout;
        } else if (item instanceof Place) {
            return  R.layout.item_all_layout1;
        }
        throw new RuntimeException("invalid obj");
    }

    
    public static class DataBoundViewHolder extends RecyclerView.ViewHolder {

        private ViewDataBinding mBinding;

        public DataBoundViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }

        public ViewDataBinding getBinding() {
            return mBinding;
        }

        public void bindTo(Object obj) {
            mBinding.setVariable(BR.data,obj);
            mBinding.executePendingBindings();
        }
        public static DataBoundViewHolder create(LayoutInflater from, ViewGroup parent, int viewType){
            ViewDataBinding viewDataBinding = null;
            if (viewType ==  R.layout.item_all_layout){
                viewDataBinding = ItemAllLayoutBinding.inflate(from,parent,false);

            }else if (viewType ==  R.layout.item_all_layout1){
                viewDataBinding = ItemAllLayout1Binding.inflate(from,parent,false);
            }
            return new DataBoundViewHolder(viewDataBinding);
        }

    }
}

 

绑定适配器和回调 (31:27)

根据民意测验,接下来讲的可能是最酷最受欢迎的特点…(我所做的)。它甚至可能是 Android 开发中最酷的功能。好吧,也许是我在炒作,哈哈。

让我们想象一下,有什么比 setText 更复杂,或许可以是一个图像的URL。你想设置为 ImageView,你需要设置一个图片的网址。当然,你不想在 UI 线程上做这件事(请记住,这些东西是在 UI 线程上进行评估)。你想用 Picasso 或其他图片加载库。于是或许你会这么做:

<ImageView …
    android:src="@{contact.largeImageUrl}" />

这基本上无法正常工作。context 要从哪里来,你把它放在什么地方?这里取不到 view。所以正确的写法应该是创建一个 BindingAdapter:

@BindingAdapter("android:src")
public static void setImageUrl(ImageView view, String url) {
    Picasso.with(view.getContext()).load(url).into(view);
}

现在这里的 BindingAdapter 是一个注解。这是为了我们设置的 android:src 和 Android 源码的关联。这是一个静态的方法,它需要2个参数。它需要一个View 和一个字符串,但注意它也可以采取其他类型。如果你想要一个不同的方法,接受一个 int 参数或 drawable,你也是可以做到的。你可以把任何你想要的填入这里。在这种情况下,我们已经支持了 Picasso 这个库的使用了。我们所有的代码都在那里。既然我们有了这个 View,我们就可以得到它的背景。我们可以写任何我们想写的代码。现在我们可以在用户界面上把图像加载出来,如你所愿。

利用属性 (33:12)

你可能想做一些更复杂的内容,比如说,我们想设置一个 PlaceHolder,设置它的图片源文件,或者它的图片 URL:

<ImageView …
   android:src="@{contact.largeImageUrl}"
   app:placeHolder="@{R.drawable.contact_placeholder}"/>

我们有两个不同的属性,同时他们对应的有两个不同的静态方法,但数据绑定却不知道,所以目前还不能直接如你所愿地进行工作。实际上,现在我们在 BindingAdapter 注解中有两个同样的属性,你只要取得这里他们的值,把他们传递给 Picasso 正确的方法即可:

<ImageView …
   android:src="@{contact.largeImageUrl}"
   app:placeHolder="@{R.drawable.contact_placeholder}"/> 
@BindingAdapter(value = {"android:src", "placeHolder"},
                requireAll = false)
public static void setImageUrl(ImageView view, String url,
                               int placeHolder) {
    RequestCreator requestCreator =
        Picasso.with(view.getContext()).load(url);
    if (placeHolder != 0) {
        requestCreator.placeholder(placeHolder);
    }
    requestCreator.into(view);
}

如果你现在有三个属性呢?有一个是 Android 源码的图片源属性,一个是占位图片属性(PlaceHolder),还有一个是设置图像的 URL 属性。所有这些不同的 BindingAdapter。真的,我有点太懒了,所以让我们做点别的了。我们可以有一个 BindingAdapter 注解需要所有这些属性,也可以是只要一个或两个,或它们的任意组合。我们所要做的就是把所需的一切都设为 false,然后我们获得所有这些参数。如果有值就会传递过来,如果没有提供,它会将它们作为默认值传递给它们。如果你没有在你的布局中写一个占位符属性,那么占位符将为零。我们在 Picasso 中调用 setter 的时候会进行检查。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值