Android Data Binding实战-入门篇

在开始之前,推荐两篇相关的博客:Data Binding(数据绑定)用户指南Android Data Binding 系列(一) – 详细介绍与使用
以及Android Developer的相关文档:Data Binding Library

建议有什么不懂的可以直接去看看文档,毕竟文档才是最好的老师。


慕客网相关学习视频:Android Data Binding实战-入门篇

声明:博客只是个人写的,用于学习与交流,与慕课网平台和授课老师没有其他任何关系。如涉及版权问题,请联系本人,将马上改正。


在使用之前,需要在Module的grade文件中添加如下代码:

开始之前需要在AS中安装插件“android binding”

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

基础用法

1、Layout文件改写

在原layout文件外套一层标签

<layout
    xmlns android=...>
</layout>

同时,还需要将命名域移到layout层(如下示例)

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.hut.example.MainActivity">

        <TextView
            android:id="@+id/tv1"
            android:textColor="#ffffff"
            android:background="#de3131"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@{VarTestBean.test1}" />

        <TextView
            android:id="@+id/tv2"
            android:textColor="#ffffff"
            android:background="#8d8686"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@{VarTestBean.test2}" />

        <Button
            android:id="@+id/bt"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:text="button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

接着,需要得到相应的Binding对象的引用,如下:

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

其中,ActivityMainBinding类的类名是根据对应的布局文件名activity_main得到的,该类是自动生成的。

之后你就可以直接通过binding对象来操作布局中的控件了,而不需要麻烦的再先使用findViewById了,示例代码如下:

binding.bt.setOnClickListener(...);

不过,需要注意的是,直接使用binding对象来操作控件时,该控件在布局文件中还是需要定义id的。


2、UI/事件绑定

① Bind UI(setVariable,setXXX)

这里为了演示,先建立一个TestBean的实体类,如下:

public class TestBean {
    private String test1;
    private String test2;

    public TestBean(String test1,String test2) {
        this.test1=test1;
        this.test2=test2;
    }

    //为了简洁,这里省略了get和set方法
}

然后在MainActivity中实例化一个TestBean作为成员变量

private TestBean bean=new TestBean("First","Second");

此时,如果我想将该bean对象的test1和test2的值分别设置给前面所提的布局文件中的中的两个TextViwe,即tv1、tv2,可以通过如下方法:

binding.tv1.setText(bean.getTest1());
binding.tv2.setText(bean.getTest2());

然而,这是刚接触Data Binding时的正常思维所想到的方法,其实,我们还可以通过另外一种方法来实现上述逻辑。

首先,需要在布局文件中的<layout></layout>标签下建立一个<data></data>标签,然后再该标签中定义对应的变量,如下:

    <data>
        <variable
            name="VarTestBean"
            type="com.hut.example.TestBean"/>
    </data>

其中,name为变量的名字,type为变量的类型,这里即为com.hut.example路径下的TestBean类型。

接着,还需要在对应的TextView中设置文本的内容:

<!--android:id="@+id/tv1"-->
<TextView
    android:textColor="#ffffff"
    android:background="#de3131"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:text="@{VarTestBean.test1}" />

上面对应的是tv1,tv2的省略。根据上面的代码,会发现android:text属性不再像平常那么去设置了,而是通过@{variable的name.成员变量},而该控件的id也可以省略了。但是,有一点需要特别注意,就是因为这里需要用到TsetBean对象的成员变量test1,因此必须要有该成员变量的get方法,否则是无法通过编译,至于原因,显而易见,在解析执行这段表达式的时候会调用相应的get方法

最后,只需要在MainActivity中添加一行代码就行了(有两种实现途径):

binding.setVarTestBean(bean);//途径1

在途径1中,binding的setVarTestBean方法同样是(即根据<variable name="VarTestBean" type="com.hut.example.TestBean"/>)自动生成的,而里面的参数则是前面的实体类对象bean。

binding.setVariable(com.hut.example.BR.VarTestBean,bean);//途径2
//binding.setVariable(BR.VarTestBean,bean);

而途径2的setVariable方法则是原有的(setVariable(int variableId, java.lang.Object value)),其中参数variableId对应的是<data></data>标签中设置的<variable/>的id,该id会自动生成,且通过BR.variable的name来引用(BR前面的路径名一般可以省略)。而参数value即为需要设定的实例对象。

上述过程,其实就是Bind UI的实现过程了。(当然,不仅仅是TextView的text属性,Button的text属性,或者EditText的hint等属性也可以通过上述方法实现,而且不是TestBean的两个成员变量都必需与控件绑定,根据需求即可,可以只对tv1实现android:text="@{VarTestBean.test1}"而tv2不进行任何操作,此时就只会设置tv1的text属性)。

② 事件绑定(方法调用和监听绑定,二者主要区别在于方法调用在编译时处理,而监听绑定于事件发生时处理。)

第一种:方法引用,实现的过程类似于手动创建Listener,相较于 android:onClick ,它的优势在于表达式会在编译时处理,如果函数不存在或者函数签名不对,编译将会报错。

为了演示实现过程,需要把前面所说的binding对象变成一个成员变量,方便在其它地方直接使用它,另外还要在前面的布局文件中添加一个EditText控件,我们这里就监听它addTextChangedListener()时注册TextWatcher监听器中的public void onTextChanged(CharSequence s, int start, int before, int count)

首先,需在MainActivity中添加一个内部类(当然也可以单独创建一个独立的类,而不是内部类,这里作为内部类是为了方便引用MainActivity中的东西),如下:

public class Presenter {
        public void onTextChangedTest(CharSequence s,int start,int before,int count) {
            bean.setTest1(s.toString()+"---1");
            bean.setTest2(s.toString()+"---2");
            binding.setVarTestBean(bean);
        }
    }

其中,匿名类的类名是自定义的,且其中的onTextChangedTest()方法名也是自定义的,但是其中的参数列表必需与TextWatcher中public void onTextChanged(CharSequence s, int start, int before, int count)的参数列表一致。至于原因稍后再说。

之后,需要在布局文件中的<data></data>标签中添加一个variable,如下:

<variable
            name="PresenterTest"
            type="com.hut.example.MainActivity.Presenter"/>

其中name自定义,type为com.hut.example.MainActivity.Presenter所对应的类型。

接着需要为EditText的事件属性绑定具体的内容:

<EditText
    android:onTextChanged="@{PresenterTest.onTextChangedTest}"
    android:background="#8bf2c9"
    android:layout_width="match_parent"
    android:layout_height="50dp" />

可以看到,EditText多了一个特殊的属性android:onTextChanged,这在本来的EditText中是不存在的,且如果不为该属性设置具体的内容(如变成android:onTextChanged="@{}"或者android:onTextChanged=""都是不能正常通过编译的),且绑定事件的属性名必需为该控件注册监听器时监听器类中对应的方法名,如为EditText添加addTextChangedListener事件监听器,此时需要实现TextWatcher的三个方法,其中就有一个为public void onTextChanged(CharSequence s, int start, int before, int count);因此绑定事件的属性名就为android:onTextChanged。而对应的持有类(即Presenter类)中的方法名可以不与上述内容相同,但是其参数列表必需与onTextChanged(CharSequence s, int start, int before, int count)一致。
而为android:onTextChanged设置的内容则是PresenterTest所拥有的onTextChangedTest方法(对应data标签中的新建的variable)。

最后,只需要在MainActivity中添加如下代码就可以实现对EditText的TextChanged事件的监听了,如下:

binding.setPresenterTest(new Presenter());

同样,setPresenterTest方法也是根据在布局文件中定义的variable自动生成的。

上述过程大概类似于将自定义的监听方法与控件本来的事件绑定在一起(这就是为什么参数列表必需一只的原因了),从在触发该事件的时候执行自定义的逻辑。例如将前文中的将EditText的TextChanged事件与自定义的onTextChangedTest方法绑定在一起,从而在TextChanged事件被出发的时候指定onTextChangedTest方法中的逻辑。
不过,暂时感觉第一中方法实现起来比较麻烦。

第二种:监听器绑定,监听绑定在事件发生时调用,可以使用任意表达式,通过lambda表达式实现,好处是可以从xml文件中回传任意参数至java代码中。

在方法引用中,方法的参数必须与监听器对象的参数相匹配。在监听绑定中,只要返回值与监听器对象的预期返回值相匹配即可。

(注意,为了简洁,第二种方法的实现示例是在实现了第一种方法的基础上进行的)
首先,同样是在内部类Presenter添加一个方法如下:

public void onClickTestListenerBinding(TestBean bean) {
            if (bean==null) 
                Toast.makeText(MainActivity.this, "回传参数为空", Toast.LENGTH_SHORT).show();
            else 
                Toast.makeText(MainActivity.this, bean.getTest1(), Toast.LENGTH_SHORT).show();
}

然后在布局文件中添加一个新的按钮,如下:

<Button
            android:onClick="@{()->PresenterTest.onClickTestListenerBinding(VarTestBean)}"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:text="button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

之后,就可以运行程序了。相比于第一种,第二种实现方法有一个好处,就是监听器绑定的方法的参数可以自定义(至于自定义的参数是从哪里获得的,在接下来的分析中再说),但是在xml文件中的实现部分写起来比较麻烦。

接下来,就来分析一下第二种方法的实现。
首先,先看到onClickTestListenerBinding(TestBean bean)方法,该方法就一个参数,为TestBean类型,那么这个参数是从哪里获得的呢?
看一下android:onClick="@{()->PresenterTest.onClickTestListenerBinding(VarTestBean)}"应该就知道一个大概了,是从xml文件中回传过来的,该参数就是前面在<data></data>标签中定义的<variable name="VarTestBean" type="com.hut.example.TestBean"/>。(因此,记得需要在MainActivity中先执行binding.setVarTestBean(bean);将bean对象传递到xml文件中,否则回传的会是一个null)
接下来,可能就会疑惑了,()->PresenterTest.onClickTestListenerBinding(VarTestBean) 又是什么鬼?其实段代码是通过lambda表达式实现的(原来的形式暂时还不知道,猜测与对应的监听器有关),其实上述表达式还有另外一种表达形式,那就是(view)->PresenterTest.onClickTestListenerBinding(VarTestBean),其中theView表示的就是当前控件的引用(因为原来实现时有onClick(View view)),只不过默认是不定义的,所以可以省略,但是如果需要在绑定的方法中只用到该控件的引用,则可以像下面的那样:

onClickTestListenerBinding(View view,TestBean bean)
android:onClick="@{(view)->PresenterTest.onClickTestListenerBinding(view,VarTestBean)}"

但是不能直接像

android:onClick="@{()->PresenterTest.onClickTestListenerBinding(view,VarTestBean)}"

这样,否则是不能通过编译的。因为需要先得到该控件自身的引用,才能进行回传。

关于第二种实现方法,再看了官网的文档之后,还有一点需要补充的。
为了简洁,是在前面代码的基础上进行的,同时为了更加直观,需在布局文件中添加一个CheckBox。
这里我们监听CheckBox的监听事件:

setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {}
});

普通实现方法就像上面的那样,但是在了解了Data Binding之后,就会想下面那样:

public class Presenter {
    public void onCheckChangedTestListener (...){}
}
<CheckBox
    android:onCheckedChanged="@{()->PresenterTest.onCheckChangedTestListener(...)}"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="10dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

这样是没有任何问题的,而
android:onCheckedChanged="@{()->PresenterTest.onCheckChangedTestListener(...)}"
其实是
android:onCheckedChanged="@{(buttonView,isChecked)->PresenterTest.onCheckChangedTestListener(...)}"的简写,当然前面的那两个参数的命名是可以自定义的,如(bv,c)都行,其本质是不会变的,但是参数列表必需与监听器(onCheckedChanged(CompoundButton buttonView, boolean isChecked))的一样,不能只写一个,如(isChecked),这样是不会通过编译的。


通过上面的补充,应该从侧面印证了我前面说的有关lambda表达式原型的猜想:
android:onClick="@{()->PresenterTest.onClickTestListenerBinding(VarTestBean)}"举例,该表达式的原型应该为

android:onClick="
@{
    (View view) {
        PresenterTest.onClickTestListenerBinding(VarTestBean)
    }
}"

其中(View view)中的参数也许可以省略,但是省略的话则不可以在{}中引用了。


至于后面的什么Data Binding原理啊、表达式什么的,我就不细说了,但是还是要啰嗦两句:
1、方法的引用除了用“.”,还可以用“::”,用后面这种更易于与对变量的引用区分开来
2、null检查,比如binding.setVarTestBean(null);是不会报异常的,因为在自动生成的相关代码中做了判空的操作,另外,对于其他基础类型的变量在不设置的时候会有默认值,如int的为0,Sting的为null(可以看作是”“,即什么都没有)。但是对于数据越界还是会报异常的。
3、使用include引用布局,比如我新建一个如下的布局文件,名字为include_test.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="VarTestBean2"
            type="com.hut.example.TestBean"/>
    </data>

    <LinearLayout
        android:layout_marginTop="10dp"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:textColor="#ffffff"
            android:background="#93eb6a"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@{VarTestBean2.test1+VarTestBean2.test2}" />
    </LinearLayout>
</layout>

然后再activity_main.xml中引用:

<include
    bind:VarTestBean2="@{VarTestBean}"
    layout="@layout/include_test" />

其中通过bind:引用布局中的variable="@{父布局中的variable}"就可应将两个variable相应的绑定在一起,另外,bind需要进行空间命名:
xmlns:bind="http://schemas.android.com/apk/res-auto"
但是使用include时尚不支持direct child,如root为merge。举个例子, 以下的代码不能正常运行 :

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.connorlin.databinding.model.User"/>
   </data>
   <merge>
       <include layout="@layout/include"
            app:user="@{user}"/>
   </merge>
</layout>

Android Data Binding实战-入门篇(补充)

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值