5. 生成绑定

生成的 binding 类会将布局中的 View 与变量绑定在一起。如前所述,binding 类的类名和包名可以自定义,binding 类也会继承 ViewDataBinding。

5.1. 创建绑定

binding 类对象应该在 inflate 之后立马创建,来确保 View 的层次结构不会在绑定前被干扰。绑定布局的方式有好几种。最常见的是使用 binding 类中的静态方法。

5.1.1 Binding 类中的 inflate 函数会 inflate View 树并将 View 绑定到 binding 对象上,一气呵成。inflate 有一个非常简单的版本,只需要一个 LayoutInflater 或一个 ViewGroup:

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

5.1.2 如果布局使用不同的机制来 inflate,下面方法则可以独立来做绑定操作:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

5.1.3 有时绑定关系是不能提前确定的,这时可以使用 DataBindingUtil :

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

它只需要一个布局文件id,会根据布局文件id自动找到 Binding 类。

5.2. 带有 ID 的 View

布局中每一个带有 ID 的 View,在 Binding 类中都会生成一个 public final 字段。binding过程会做一个简单的赋值,在 binding 类中保存对应 ID 的 View。这种机制相比调用 findViewById 效率更高。例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="user" type="com.example.User"/>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.firstName}"
                android:id="@+id/firstName"/>
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.lastName}"
                android:id="@+id/lastName"/>
    </LinearLayout>
</layout>

将会在 binding 类内生成:

public final TextView firstName;
public final TextView lastName;

ID 在 data binding 中并不是必需的,但是在某些情况下还是有必要在 java 代码中对 View 进行操作,这时就需要 id,在 java 代码中可以通过 binding 对象直接操作 view 对象。

5.3. 变量

每一个变量会有相应的存取函数:

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

会在 binding 类中生成对应的 getter setter:

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

5.4. ViewStub

ViewStub 相比普通 View 有一些不同。ViewStub 一开始是不可见的,当它们被设置为可见,或者调用 inflate 方法时,ViewStub 会被替换成另外一个布局。

因为 ViewStub 实际上不存在于 View 结构中,binding 类中的 View 对象也得移除掉,以便系统回收。因为 binding 类中的 View 都是 final 的,所以我们使用一个叫 ViewStubProxy 的类来代替 ViewStub。开发者可以使用它来操作 ViewStub,获取 ViewStub inflate 时得到的视图。

但 inflate 一个新的布局时,必须为新的布局创建一个 binding。因此,ViewStubProxy 必须监听 ViewStub 的 ViewStub.OnInflateListener,并及时建立 binding。由于 ViewStub 只能有一个 OnInflateListener,你可以将你自己的 listener 设置在 ViewStubProxy 上,在 binding 建立之后, listener 就会被触发。

<?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>

    </data>

    <LinearLayout
        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:orientation="vertical"
        tools:context="com.connorlin.databinding.context.ViewStubActivity">

        <Button
            android:text="@string/inflate_viewstub"
            android:onClick="inflate"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/include"
            android:layout_width="match_parent"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

像下面这样在 OnInflateListener 中建立绑定:

public class ViewStubActivity extends BaseActivity {

    private ActivityViewStubBinding mActivityViewStubBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivityViewStubBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
        mActivityViewStubBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                // 填充完成开始创建 binding
                IncludeBinding viewStubBinding = DataBindingUtil.bind(inflated);
                User user = new User("Connor", "Lin", 28);
                viewStubBinding.setUser(user);
            }
        });
    }

    public void inflate(View view) {
        if (!mActivityViewStubBinding.viewStub.isInflated()) {
            // 触发 viewstub 填充
            mActivityViewStubBinding.viewStub.getViewStub().inflate();
        }
    }
}

当点击 Button 的时候会上面的调用 inflate 方法,此时触发 viewstub 开始填充 view,并且给 viewstub 设置了监听器,当 view 填充完成,开始创建 binding。

5.5. 高级绑定

5.5.1. 动态绑定变量

有时候,我们不知道 binding 类要绑定哪一个变量。例如,RecyclerView.Adapter 可以用来处理不同布局的时候,它就不知道应该使用 binding 类绑定那一个变量。而是在 onBindViewHolder(VH, int)) 的方法中,binding 类重新被赋值,来更新 item view 的数据。

在下面例子中,RecyclerView 中的所有布局都内置了一个 item 变量。BindingHolder 有一个 getBinding 方法,返回一个 ViewDataBinding 基类。

@Override
public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    RecyclerItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.recycler_item, parent, false);

    Presenter presenter = new Presenter();
    binding.setPresenter(presenter);

    BindingHolder holder = new BindingHolder(binding.getRoot());
    holder.setBinding(binding);
    return holder;
}

@Override
public void onBindViewHolder(BindingHolder holder, int position) {
    // 动态绑定变量
    holder.getBinding().setVariable(BR.item, mRecyclerItemList.get(position));
    holder.getBinding().executePendingBindings();
}

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

public class BindingHolder extends RecyclerView.ViewHolder {
    private RecyclerItemBinding binding;

    public BindingHolder(View itemView) {
        super(itemView);
    }

    public RecyclerItemBinding getBinding() {
        return binding;
    }

    public void setBinding(RecyclerItemBinding binding) {
        this.binding = binding;
    }
}

5.5.2. 立即 binding

当变量或者 observable 发生变动时,会在下一帧触发 binding。有时候 binding 需要马上执行,这时候可以使用 executePendingBindings())。

5.5.3. 后台线程问题

只要数据不是容器类,你可以直接在后台线程做数据变动。DataBinding 会将变量/字段转为局部量,避免同步问题。

results matching ""

    No results matching ""