美文网首页
Data Binding 详解(四)-生成的绑定类

Data Binding 详解(四)-生成的绑定类

作者: 汤谷的扶桑 | 来源:发表于2019-07-12 17:55 被阅读0次

    知是行之始,行是知之成。
    文章配套的 Demohttps://github.com/muyi-yang/DataBindingDemo
    Demo 支持 Java 和 Kotlin 双语言,master 分支为 Java 语言代码,kotlin 分支为 Kotlin 语言代码。

    Data Binding 生成用于访问布局变量和视图的绑定类,它将布局变量与布局中的视图链接起来。默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。比如布局文件名是 activity_main.xml 相应生成的类 ActivityMainBinding,也可以自定义绑定类的名称和包。 所有以 <data> 为根标签的布局都会生成绑定类,都继承自 ViewDataBinding
    类。这个类包含从布局属性(例如,声明的变量)到布局视图的所有绑定,并且知道如何为绑定表达式分配值,有兴趣的同学可以看看生成后的类是怎么实现的。

    创建绑定对象

    创建绑定类的对象有两种方式,一种是使用 DataBindingUtil 工具类,一种是直接使用绑定类的静态方法来获取。

    在 Activity 中我们一般使用 DataBindingUtil 工具类来获取绑定对象,因为它有一个 setContentView 方法,里面调用了 Activity 的 setContentView 方法并返回了绑定类对象,比如:

    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    

    也可以通过 DataBindingUtil 工具类的 inflate 方法获取:

    ActivityMainBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_main, null, false);
    

    但在其他地方(Fragment、ListView 或 RecyclerView 等)我们一般会直接使用绑定类的静态方法来获取,比如:

    LayoutCelebrityItemBinding binding = LayoutCelebrityItemBinding.inflate(inflater);
    // 或者
    LayoutCelebrityItemBinding binding = LayoutCelebrityItemBinding.inflate(inflater, viewGroup, false);
    

    有时你可能需要使用老方式 inflate 一个布局,你可以这样获取绑定对象:

    View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
    MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot)
    

    有时无法事先知道绑定类型,布局可能是动态的。 在这种情况下,可以使用 DataBindingUtil 类创建绑定,如下面的代码片段所示:

    View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
    ViewDataBinding binding = DataBindingUtil.bind(viewRoot)
    // 或者
    ViewDataBinding binding = DataBindingUtil.inflate(getLayoutInflater(), layoutId, parent, attachToParent);
    

    至此我们了解了两个获取绑定类对象的两种方式,可以根据不同的场景运用不同的方式,其实绑定类中的 inflate 方法的最终也是调用 DataBindingUtil 的 inflate 方法,bind 方法也是一样,有兴趣的同学可以阅读一下生成的绑定类代码。

    带 ID 的 View

    Data Binding 在绑定类中为每个在布局中具有 ID 的 View 创建不可变字段。例如,Data Binding 从以下布局创建类型为 TextView 的 tvInfo,类型为 Button 的 btnBinding 字段:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
         ...
        <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
            <TextView
                android:id="@+id/tv_info"
                ... />
            <Button
                android:id="@+id/btn_binding"
                ... />
            <Button
                android:id="@+id/btn_list"
                ... />
        </android.support.constraint.ConstraintLayout>
    </layout>
    

    在绑定类中已经生成了相应的字段,我们不在需要调用 findViewById () 方法获取,可以直接从绑定类中获取:

    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    binding.tvInfo.setText("我是使用Data Binding的Demo");
    

    变量

    Data Binding 为布局中声明的每个变量生成访问方法。 例如,下面的布局在绑定类中为 user、 index 变量生成 setter 和 getter 方法:

    <!--activity_user.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/tools">
        <data>
            <import type="com.example.databindingdemo.bean.UserInfo" />
            ...
            <variable
                name="user"
                type="UserInfo" />
    
            <variable
                name="index"
                type="int" />
         ...
        </data>
        ...
    </layout>
    

    在获取了绑定类对象的地方可以通过 setter 方法设置数据,通过 getter 方法获取数据:

    public class UserActivity extends AppCompatActivity {
    
        private ActivityUserBinding binding;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
            ...
            binding.setUser(info);
            binding.setIndex(1);
            ...
        }
    }
    

    ViewStub的使用

    ViewStub 与普通 View 不同,它开始时是一个不可见的 View。 当设置可见或者调用 inflate() 方法时,它们会在布局中通过 inflate 另一个布局来替换自己。

    因为 ViewStub 中的布局实际上在 View 层次结构中并没有加载,所以在绑定对象中也不能直接创建 ViewStub 中的布局对象,必须要在使用的时候再创建,所以绑定类中使用 ViewStubProxy 对象取代了 ViewStub,你可以使用它来访问 ViewStub,当 ViewStub 被创建和加载后你可以通过它来访问具体的布局结构。以下为布局:

    <!--activity_binding.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        ...
            <ViewStub
                android:id="@+id/vs_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout="@layout/layout_bar" />
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="@{()->activity.showViewStub()}"
                android:text="@string/show_stub" />
        ...
    </layout>
    
    <!--layout_bar.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable name="resId" type="int" />
            <variable name="name" type="String" />
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:layout_margin="10dp"
                android:src="@drawable/default_mini_avatar"
                app:image="@{resId}" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="10dp"
                android:gravity="center"
                android:text="@{name, default=@string/default_name}"
                android:textSize="20sp" />
        </LinearLayout>
    </layout>
    

    这里在布局中声明了一个 ViewStub,并为它设置 layout_bar.xml 布局,同时声明了一个 Button 并为它设置了点击事件(调用 showViewStub() 方法)。

    当你想使用 ViewStub 中的布局时,你需要获取到里面的绑定类对象,你可以向 ViewStubProxy 设置一个 OnInflateListener 监听器,然后在监听器回调中获取绑定类。比如:

    public class BindingClassActivity extends AppCompatActivity {
        private ActivityBindingBinding binding;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = DataBindingUtil.setContentView(this, R.layout.activity_binding);
            binding.vsBar.setOnInflateListener(new ViewStub.OnInflateListener() {
                @Override
                public void onInflate(ViewStub stub, View inflated) {
                    LayoutBarBinding vsBarBinding = DataBindingUtil.bind(inflated);
                    vsBarBinding.setName("木易");
                    vsBarBinding.setResId(R.drawable.head);
                }
            });
           ...
        }
        ...
        public void showViewStub() {
            ViewStub viewStub = binding.vsBar.getViewStub();
            if (viewStub != null) {
                viewStub.inflate();
            }
        }
        ...
    }
    

    可以看到 showViewStub() 方法中调用 ViewStub 的 inflate() 方法,当 Button 被点击时就会触发布局的加载,加载完成后会触发 OnInflateListener 的回调,然后在回调方法中通过 DataBindingUtil.bind(inflated)获取到了 ViewStub 中的布局绑定类,继而进行数据绑定。

    立即绑定

    当变量或 observable 对象数据发生变化时,数据绑定将在 View 的下一帧刷新之前更改。 但是,有时必须立即执行数据绑定。 若要强制执行,可以使用 executePendingBindings ()方法。一般情况不需要这么做。

    动态变量

    有时候你的布局文件是动态的,比如在一个 RecyclerView 中展现一系列新闻内容,有些内容是带图片的,有些是纯文本,这时我们可以采用多个 item 布局文件来呈现不一样的 UI 效果。比如:

    public class NewsAdapter extends RecyclerView.Adapter {
        ...
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
            ViewDataBinding binding;
            if (viewType == VIEW_TYPE_TEXT) {
                binding = LayoutNewsItemTextBinding.inflate(inflater, viewGroup, false);
            } else {
                binding = LayoutNewsItemPictureBinding.inflate(inflater, viewGroup, false);
            }
            return new NewsViewHolder(binding.getRoot(), binding);
        }
        ...
    }
    

    这里通过类型判断,分别使用了 layout_news_item_text.xmllayout_news_item_picture.xml 布局文件。这两个布局文件 UI 展现不一样,但需要的数据类型都是 NewsInfo:

    <!--layout_news_item_picture.xml-->
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable
                name="info"
                type="com.example.databindingdemo.bean.NewsInfo" />
        </data>
        ...
    </layout>
    
    <!--layout_news_item_text.xml-->
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable
                name="info"
                type="com.example.databindingdemo.bean.NewsInfo" />
        </data>
        ...
    </layout>
    

    这种情况下我们在 RecyclerView.Adapter 的 onBindViewHolder() 方法中就无法准确的知道绑定类类型,但是我们任然要为其绑定数据,我们可以这样做:

    public class NewsAdapter extends RecyclerView.Adapter {
        private List<NewsInfo> data = new ArrayList<>();
        ...
        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
            NewsViewHolder holder = (NewsViewHolder) viewHolder;
            ViewDataBinding binding = holder.binding;
            binding.setVariable(BR.info, data.get(position));
        }
        ...
    }
    

    在这里我们并没有获取具体的绑定类,而是获取了 ViewDataBinding 类,它是一个抽象类,是所有绑定类的父类。它提供了一个 setVariable(int variableId, Object value) 方法(第一个参数为布局中声明的绑定变量 ID,第二个参数为要绑定的数据),通过这个方法我们可以动态的为布局中的变量绑定相应的数据。

    BR 是 Data Binding 自动生成的资源 ID 文件,它包含所有的数据绑定变量的 ID,类似于 Android 的 R 文件。在上面例子中,RB.info 是布局中 info 变量的 ID。

    后台线程

    你可以在后台线程中更改数据,只要它不是一个集合。Data Binding 会在计算时将每个变量/字段在各个线程中做一份数据拷贝,以避免同步问题。

    自定义绑定类名称

    默认情况下 Data Binding 将根据布局文件的名称生成绑定类,以大写字母开头,删除下划线,驼峰格式命名,并添加 Binding 后缀。该类放在模块包下的 databinding 包中。例如,布局文件 layout_custom.xml 生成 LayoutCustomBinding类,放在 com.example.databindingdemo.databinding包中。

    通过调整 data 标签的 class 属性,可以重命名绑定类或将绑定类放在不同的包中。例如,以下布局在当前模块的 databinding 包中生成绑定类 MyCustom:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data class="MyCustom">
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </layout>
    
    效果图: image.png

    你可以通过在类名前加一个句点来使生成绑定类在模块包中:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data class=".MyCustom">
        </data>
        ...
    </layout>
    
    效果图: image.png

    你还可以在类名前使用完整的包名。下面的示例在 com.custom 包中创建 MyCustom 绑定类:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data class="com.custom.MyCustom">
        </data>
        ...
    </layout>
    
    效果图: image.png

    此篇到这里就结束了,可以查看下一篇 Data Binding 详解(五)-绑定适配器

    如果你觉得文章有帮助到你,记得点个喜欢以表支持,同时欢迎你的指正和建议。十分感谢!

    相关文章

      网友评论

          本文标题:Data Binding 详解(四)-生成的绑定类

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