DataBinding学习笔记(一)

2017-10-09  本文已影响0人  Ursus_M

引入DataBinding

要在当前module的build.gradle文件中添加如下代码

android{
    ...
    dataBinding {
        enabled = true
    }
    ...
}

快速使用

第一步 :创建对象

一个普通的java对象即可

public class User {
    public String name;
    public String phone;
}

第二步 :修改布局

  1. 规范布局:在布局文件最外层添加一个 layout 的根标签
  2. 引入数据:在 layout 标签下,添加 data 标签
  3. 声明对象:在 data 标签中添加 variable 的标签,其中 name 表示对象名,type 表示类名(包含包名)
  4. 关联属性:通过表达式 “@{}” 获取对象的属性,并将他们绑定到控件中
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <data>
        <variable name="user" type="cn.com.ursus.User"/>
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"/>
    </RelativeLayout>
</layout>

注意事项

  1. User 中的有公有属性 name 时,@{user.name} 相当于 user.name
    User 中的无公有属性 name 时,@{user.name} 相当于 user.getName()

  2. android.text 绑定属性的时候,注意转成字符串,如果是整型,会被当成资源id处理,可以参考下面的代码(字符串用双引号 " " 原先外面那层双引号转成单引号' '

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{student.age + ""}'/>

第三步 :绑定对象

  1. ActivityonCreate 方法中,用 DataBindingUtil.setContentView 来替换原来的 setContentView ,得到一个名为 ActivityMainBinding 对象。( ActivityMainBinding 对象是根据布
    局文件自动生成的,名称来自于布局文件的名称配合上驼峰规则。)
  2. 通过刚才生成的 ActivityMainBinding 将和布局绑定的对象设置进去
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    User user = new User();
    user.name = "luffy";
    user.phone = "130****5678";
    binding.setUser(user);
}

这样一个最基本的数据绑定就完成了。

Observable

绑定完之后,肯定希望的是 User 对象中的属性值改变之后,绑定的控件也跟着自动刷新,然而并没有,于是乎,需要对 User 对象进行如下改造。

public class User extends BaseObservable{

    private String name;
    private String phone;

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

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    
    @Bindable
    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
        notifyPropertyChanged(BR.phone);
    }
}

注解 @Bindable 修饰 getName 方法可在 BR 类中自动生成一个对应属性 name 的整型常量 BR.name。 使用 notifyPropertyChanged 方法即可刷新绑定改属性的控件。至于 BR 是什么,可以类比为 Android 中的 R

如果一个类中只有个别属性别绑定到ui,需要即使刷新,而整个类又不想继承
BaseObservable ,可以使用 ObservableField , 具体可以参考下面的代码

public class Student {
    public final ObservableField<String> name = new ObservableField<>();
    public final ObservableField<String> grade = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}

...

final Student student = new Student();
student.name.set("Ace");
student.grade.set("grade2");
student.age.set(1);
binding.setStudent(student);

表达式和事件

前面提过为了避免 android:text 将整型识别为资源文件,需要将整型转成字符串。

android:text='@{student.age + ""}'

由此可见在 @{} 中进行一些简单的表达式操作。

三目运算符 ?:

android:text="@{ user.phone != null ? user.phone : @string/no_phone}"

Null Coalescing Operator ??

这个不是 java 代码的语法,Databinding 自定义的,类似于三目运算符特殊情况的一种简易写法

android:text="@{ user.phone ?? @string/no_phone}"

这和上面那种写法是等价的

使用静态属性和静态方法

上面代三目运算符的例子,如果我们要控制某个控件的显示与否可以这么写

android:visibility="@{ user.phone != null ? View.GONE : View.VISIBLE}"

这里不可以使用 gonevisible , 必须使用 View.GONEView.VISIBLE
可是这个 View 是哪里来的? 我们可以在 data 标签中 import 进来

<data>
    <import type="android.view.View"/>
    <import type="android.text.TextUtils"/>
</data>

import 进来之后,我们就可以也只能使用其中的静态属性静态方法

android:visibility="@{TextUtils.isEmpty(user.phone) ? View.GONE : View.VISIBLE}

注意:
如果两个 import 进来的两个类,类名相同,我们可以给他们设置别名

<import alias="MainActivityPresenter"
        type="cn.com.ursus.PermissionUtils"/>
<import alias="ActivityPresenter"
        type="cn.com.ursus.presenter.PermissionUtils"/>

资源文件

上面的几个例子中在 @{} 中用到了 @string 资源文件,那么可以使用带占位符的 @string 吗?当然可以

<string name="welcome_name">Welcome,%s</string>
...
android:text="@{@string/welcome_name(user.name)}"

当然除了 @string@dimen@color 等资源文件也肯定是支持的

事件

我们可以在 @{} 中可以用表达式来响应事件,比如最常用的 onClick,我们可以之间在之前的 User 类中编写相应的方法来响应,不过此处我重新创建一个类专门处理响应事件。

class Presenter{
    public void clickUserName(View v){...}
    public void userNameChanged(CharSequence s, int start, int before, int count) {...}
}
...
android:onClick="@{presenter.clickUserName}"
android:onTextChanged="@{presenter.userNameChanged}"

在 Databinding 中只需要响应 onTextChanged ,无需理会 beforeTextChanged afterTextChanged ,然而如果在代码中实现 onTextChanged 我们一般都会采用如下的方式,就会显得有些臃肿。

tvName.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {

    }
});

我们自定义的响应方法必须和常规监听方法保持一致的参数吗?答案是否定的,我们可以自己定义响应方法的参数,不过表达式和先前略有不同,类似lambda

public void clickUserName(View v,String username){
    ...
}

...
android:onClick="@{(v)->presenter.clickUserName(v,user.name)}

还有一些表达式就不一一列举了,下面是从官方Data Binding Guide上复制下来的目前 @{} 支持的表达式

自定义属性

自动属性

我们现在自定义了一个控件,其中有一个如下的 setPhoneNumber 方法

public class MyTextView extends TextView {
    ...
    public void setPhoneNumber(String phone) {
      if (!isPhoneNumber(phone)) {
         throw new IllegalArgumentException("手机号格式不正确");
      }
      String show = phone.substring(0, phone.length() - (phone.substring(3)).length())
              + "****"
              + phone.substring(7);
      setText(show);
    }
    ...
}

神奇的一幕发生了 ,我们可以直接在布局文件中使用 phoneNumber 的布局属性,虽然 MyTextView 中并没有该属性

<cn.com.ursus.view.MyTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:phoneNumber="@{user.phone}"/>

自定义属性

现在有个需求,项目用的 Picasso 图片框架,我们需要在ImageView中自定义一个布局属性,使得我们可以给该属性设置一个网络url时,自动使用 Picasso 图片框架来加载网络图片,该如何做?我们只需写一个静态方法,给他打上一个 @BindingAdapter

@BindingAdapter({"image_url"})
public static void setImageUrl(ImageView view, String url){
   Picasso.with(MainApplication.getContext())
            .load(url)
            .placeholder(R.mipmap.ic_launcher)
            .into(view);
}

然后就可以在布局中使用该属性了

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:image_url="@{user.icon}"/>

那么这个 setImageUrl 方法该放在哪个类里呢?需要将那个类导入布局么?
其实 setImageUrl 方法可以放在任意类里面,而且不需要导入布局中,不过同一个控件的自定义属性一般放在一起管理。

上一篇下一篇

猜你喜欢

热点阅读