Android Jetpack架构组件之数据绑定(DataBinding)入门

我们绝对不会花费任何多余的时间和体力在无意义的事情上,因为我们的眼睛永远只盯着猎物 ————《狼道》
目录
前言
一、简介
二、基本配置
三、简单使用
四、Demo简介
五、基础用法
六、语法
七、插件支持
八、Demo地址
九、参考文档
十、内容推荐


前言

刚认识DataBinding的时候是在MVVM模式上看到的,之前一直都是在使用MVP模式。之后想换个MVVM模式试试,看是否和别人说的一样比MVP好用,简洁。但是刚接触就遇到一个砍(DataBinding),然后就犹如你看到的一样,有了这篇文章。这篇文章主要是介绍了DataBinding基础用法,对DataBinding有一个粗略的了解。也是为我后面学习MVVM模式做个铺垫吧,文章如有不足,请多多指教。好了,废话不多说,接招吧!!

一、简介

早在2015谷歌 I/O大会上,介绍了一个新的框架DataBinding,从名字就可以看出来,这是一个数据绑定库。借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源

那么问题来了:

(1)数据绑定是什么?

将一个用户界面元素(控件)的属性绑定到一个类型(对象)实例上的某个属性的方法

简而言之,把代码中的数据和xml(UI)绑定起来,双方都能对数据进行操作,并且在数据发生变化的时候,自动刷新数据。

(2)数据绑定有什么用?

数据绑定分为单向绑定和双向绑定两种方式

单向绑定就是说数据的流向是单方面的,只能从代码流向UI;

双向绑定的数据流向是双向的,当业务代码中的数据改变时,UI上的数据能够得到刷新;当用户通过UI交互编辑了数据时,数据的变化也能自动的更新到业务代码中的数据上。

而DataBinding就是实现数据绑定的一个框架

(3)DataBinding有什么用?

  • 把代码中的数据和xml(UI)绑定起来,双方都能对数据进行操作,并且在数据发生变化的时候,自动刷新数据
  • 借助布局文件中的绑定组件,您可以移除 Activity 中的许多界面框架调用,使其维护起来更简单、方便。还可以提高应用性能,并且有助于防止内存泄漏以及避免空指针异常

(4)优点

  1. 您可以移除 Activity 中的许多界面框架调用,使其维护起来更简单、方便
  2. 提高应用性能,并且有助于防止内存泄漏以及避免空指针异常
  3. 减少大量重复的代码,去除Activity/Fragment中的UI代码

了解DataBinding是什么后,我们就可以开始搬砖了。

二、基本配置

1、环境要求:

  1. 系统版本:Android 2.1(API level 7)及以上
  2. Gradle版本:1.5.0-alpha1及以上
  3. Android Studio版本:1.3及以上

2、app要使用Data Binding,需要添加Data Binding到gradle构建文件里

App module - build.gradle

android {
   ...

    dataBinding{
        enabled = true
    }
}

这样就可以在项目中使用

三、简单使用

步骤:
(1)在布局中最外部套一层layout标签

activity_main.xml

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

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/app_name" />

    </android.support.constraint.ConstraintLayout>
</layout>

(2)关联布局

//绑定布局 替代setContentView(R.layout.activity_main);
ActivityMainBinding bind = DataBindingUtil.setContentView(this,R.layout.activity_main);
//给指定id赋值
bind.btn.setText("xxxxx");

这样算是绑定完成。是不是比butterknife更简洁.. 当然不只这些。

这里以一个Demo,看看DataBinding常用的几种方式。

四、Demo简介

1、效果图:

2、主页布局activity_main.xml

<?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="click"
            type="android.view.View.OnClickListener"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <EditText
                android:id="@+id/et_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:gravity="center"
                android:layout_weight="1"
                android:inputType="text"
                android:hint="联系名字?" />
            <EditText
                android:id="@+id/et_phone"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:gravity="center"
                android:inputType="number"
                android:layout_weight="1"
                android:hint="联系号码?"
                />
            <!--点击事件设置-->
            <Button
                android:id="@+id/btn_add"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="@{click}"
                android:text="添加联系人"
                />
        </LinearLayout>
        <!--联系人列表-->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>

3、MainActivity.class

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ActivityMainBinding bind;
    private List<User> list=new ArrayList<>();
    private MyAdapter myAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //关联布局
        bind= DataBindingUtil.setContentView(this,R.layout.activity_main);
        //设置点击事件
        bind.setClick(this);
        //recycler适配
        LinearLayoutManager manager = new LinearLayoutManager(this);
        bind.recycler.setLayoutManager(manager);
        myAdapter = new MyAdapter(this,list);
        bind.recycler.setAdapter(myAdapter);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_add:
                User user = new User();
                user.setName(bind.etName.getText().toString());
                user.setPhone(bind.etPhone.getText().toString());
                user.setImgUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1545725899676&di=f875404bfb91dc586cf14030097b7a35&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Fzj%2Fpics%2Fhv1%2F112%2F112%2F2259%2F146920147.jpg");
                list.add(user);
                myAdapter.notifyItemInserted(list.size());
                break;
        }
    }
}

4、User.class

public class User {
    private String name;//姓名
    private String phone;//电话
    private String imgUrl;//头像
    public String getImgUrl() {    return imgUrl;    }

    public void setImgUrl(String imgUrl) {    this.imgUrl = imgUrl;    }

    public String getName() {    return name;    }

    public void setName(String name) {    this.name = name;    }

    public String getPhone() {    return phone;    }

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

5、适配器布局adapter_main.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="user"
            type="lwb.blcs.databinding.User"/>
        <!--定义点击事件-->
        <variable
            name="click"
            type="lwb.blcs.databinding.MyOnClickListen"/>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">
        <!--给控件赋值单向绑定@{}  双向绑定@={}-->
        <!-- 给点击事件传值user.name -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:onClick="@{()->click.onClickName(user.name)}"
            android:layout_weight="1"
            android:text="@={user.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:onClick="@{()->click.onClickPhone(user.phone)}"
            android:text="@={user.phone}" />

        <!-- 设置点击事件-->
        <!-- 设置自定义图片属性 imageUrl-->
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:onClick="@{click.onClickImage}"
            app:imageUrl="@{user.imgUrl}"
            />
    </LinearLayout>
</layout>

6、适配器MyAdapter.class

class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private MainActivity mainActivity;
    private List<User> list;
    private final LayoutInflater inflater;

    public MyAdapter(MainActivity mainActivity, List<User> list) {
        this.mainActivity = mainActivity;
        this.list = list;
        inflater = LayoutInflater.from(mainActivity);
    }
    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        //MyAdapter适配器绑定布局
        AdapterMainBinding bind = DataBindingUtil.inflate(inflater, R.layout.adapter_main, viewGroup, false);
        return new MyViewHolder(bind);
    }
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int i) {
        //绑定数据
        holder.bind.setUser(list.get(i));
        //设置点击事件
        holder.bind.setClick(new MyOnClickListen(mainActivity));
    }
    @Override
    public int getItemCount() {
        return list.size();
    }
    class MyViewHolder extends RecyclerView.ViewHolder{
        //ViewHolder 构造函数修改
        AdapterMainBinding bind;
        public MyViewHolder(@NonNull AdapterMainBinding itemView) {
            //itemView.getRoot()获取View
            super(itemView.getRoot());
            this.bind=itemView;
        }
    }
}

7、ImageUtils.class

    /**
     * 1.加载图片,无需手动调用此方法
     * 2.使用@BindingAdapter注解设置自定义属性的名称,imageUrl就是属性的名称,
     * 当ImageView中使用imageUrl属性时,会自动调用loadImage方法,
     */
    @BindingAdapter({"imageUrl"})
    public static void loadImage(ImageView imageView, String url) {
        Glide.with(imageView.getContext())
                .load(url)
                .into(imageView);
    }

8、MyOnClickListen.class

public class MyOnClickListen {
    private Activity mainActivity;
    public MyOnClickListen(Activity mainActivity) {
        this.mainActivity = mainActivity;
    }

    //方法引用
    public void onClickName(String name){
        Toast.makeText(mainActivity,name,Toast.LENGTH_SHORT).show();
    }
    //方法引用
    public void onClickPhone(String phone){
        Toast.makeText(mainActivity,phone,Toast.LENGTH_SHORT).show();
    }
    //监听器绑定
    public void onClickImage(View view){
        Toast.makeText(mainActivity,"图片",Toast.LENGTH_SHORT).show();
    }
}

9、基础信息

    //相关依赖
   implementation 'com.android.support:recyclerview-v7:28.0.0'
   implementation 'com.github.bumptech.glide:glide:4.8.0'
    //网络权限
    <uses-permission android:name="android.permission.INTERNET"/>

简单Demo就完成,省了大量的findViewById(),当然布局的使用也有点不习惯。不过相对代码简洁了很多。

不过在写demo过程中也到过更新不及时,无法等到对象的情况。需Clean Project

五、基础用法

当然Databinding用法还有很多,上面demo只是简单尝试。更多使用需要查看官网API

(1)bind UI

<data>
    <variable
        name="user"
        type="lwb.blcs.databinding.User" />
</data>
//activity_main布局外层添加layout标签后自动生成ActivityMainBinding
ActivityMainBinding bind = DataBindingUtil.setContentView(this,R.layout.activity_main);

User user = new User("张si", "12");

//布局variable 定义属性name 则代码中可绑定数据

//绑定数据的第一种用法
//bind.setVariable(BR.user,user);
//绑定数据的第二种用法 
bind.setUser(user);

(2) bind 点击事件

1.常用方法:

<data>
    <variable
        name="click"
        type="android.view.View.OnClickListener"/>
</data>
<Button
    android:id="@+id/btn_add"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{click}"
    android:text="添加联系人"
/>
//关联布局
ActivityMainBinding bind = DataBindingUtil.setContentView(this,R.layout.activity_main);
//绑定点击事件
bind.setClick(this);
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_add:
                ....
                break;
        }
    }

2.方法引用

<data>
    <variable
        name="click"
        type="lwb.blcs.databinding.MainActivity.MyOnClickListen" />
</data>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{click.onClickAge}"
    android:layout_marginTop="20dp"
    android:text="@{user.age}" />

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{()->click.onClickText(user)}"
    android:text="@{user.name}" />
ActivityMainBinding bind = DataBindingUtil.setContentView(this,R.layout.activity_main);
bind.setClick(new MyOnClickListen());

//点击事件处理
public class MyOnClickListen{
    //方法引用
    public void onClickAge(View view){
        Toast.makeText(MainActivity.this,"点我onClickAge",Toast.LENGTH_SHORT).show();
    }
    //监听器绑定
    public void onClickText(User user){
        Toast.makeText(MainActivity.this,user.name,Toast.LENGTH_SHORT).show();
    }
}

(3) 图片显示

1、BindingAdapter注解设置自定义属性

/**

* 1.加载图片,无需手动调用此方法

* 2.使用@BindingAdapter注解设置自定义属性的名称,imageUrl就是属性的名称,

* 当ImageView中使用imageUrl属性时,会自动调用loadImage方法,

*/

@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView imageView, String url) {
    Glide.with(imageView.getContext()).load(url)
    .error(R.mipmap.error)
    .into(imageView);
}

<!-- 当imageUrl属性存在时,会自动调用ImageHelper的loadImage方法 -->

<ImageView
    android:layout_width="120dp"
    android:layout_height="120dp"
    android:scaleType="centerCrop"
    app:imageUrl="@{user.picUrl}" />

六、语法

(1)variable | Import

在data标签下可以使用多个import标签和variable标签:

  1. variable 标签是约定数据的引用对象:语法为 <variable name="变量名" type="类型"/>
  2. import 标签是引入数据类型:语法为 <import type="类型"/>
    <data>
        <import type="android.view.View"/>
        <import type="java.util.List"/>
        <!--这里小于号和大于号 需要用转义字符-->
        <variable name="userList" type="List&lt;String&gt;"/>
        <variable name="user" type="lwb.blcs.databinding.User"/>
    </data>

    <TextView
            android:text="@{userList[0]}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="@{user.isShow ? View.VISIBLE : View.GONE}"/>

(2)设置别名alias

路径不同,但名称相同的类,可以借助于别名来解决,别名借助alias字段来标识:语法:<import type="类型" alias="别名">

<import type="lwb.blcs.databinding.User" />
<import type="lwb.blcs.databinding.bean.User" alias="UserBean"/>

<variable
    name="user"
    type="User"/>
<variable
    name="user"
    type="UserBean"/>

(3)自定义Binding类名称

在默认情况下,绑定类根据布局文件的名称生成,命名规则:布局名首字母,去掉下划线,下划线后的首字母大写 ,并加上后缀 Binding。该类放在 databinding模块包下的包中。例:activiy_main.xml ==> ActivityMainBinding

我们可以通过调整data元素的class属性,将 binding 类进行重命名或放置在不同的包中

//会放在默认生成的databinding包中
<data class="ActMain">
    ......
</data>

//在包名中生成bind类
<data class=".ActMainBind">
    ......
</data>

//为生成binding类指定路径  使用完整的包名称
<data class="lwb.blcs.databinding.ActMainBinding"> 
    ......
</data>
//默认生成的Binding类
ActivityMainBinding bind= DataBindingUtil.setContentView(this,R.layout.activity_main);

//自定义后生成的Binding类
ActMain bind= DataBindingUtil.setContentView(this,R.layout.activity_main);

(4)Observable

Observable classes provide a way in which data bound UI can be notified of changes.

译为:提供了一种可以向数据绑定UI通知更改的方法

Data Binding绑定UI后更改数据并不能更新到UI。所以Data Binding提供了Observable来自动更新数据。

Observable分为三类:

  1. Observable objects
  2. Observable fields
  3. Observable collections

主要作用:当数据发生改变的时候,可以及时更新UI。

1、Observable objects

Observable是一个java接口,DataBinding基于此接口提供了一个基础类BaseObserable

public class User extends BaseObservable{
    private String name;
    @Bindable
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
}

步骤:

(1)继承BaseObserable

(2)getter添加Bindable注解

(3)setter方法中使用notifyPropertyChanged提醒UI更新数据

2、Observable fields

一个对象包装器,使其可观察。可以使用可观察的字段类自动更新该字段;

public class User {
    public ObservableField<String> name = new ObservableField<>();
}

//使用方法
User user = new User();
user.name.set("更新");

当然ObservableField<T>中传入的泛型可以是Java中的基本类型;还可以使用 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable等具体的类型,效果和ObservableField<T>一样。

3、Observable collections

顾名思义:可观察的集合,当数据发生变化时可以及时通知UI更新。

使用方式有两种:
ObservableArrayList
ObservableArrayMap

<data>
    <import type="android.databinding.ObservableMap" />
    <import type="android.databinding.ObservableList" />
    <variable
        name="map"
        type="ObservableMap<String, String>" />
    <variable
        name="list"
        type="ObservableList<String>" />
</data>

ObservableArrayMap<String, String> map = new ObservableArrayMap<>();

ObservableArrayList<String> list= new ObservableArrayList<>();

(5)绑定表达式语言中可以使用以下运算符和关键字

算数 + - / * %
字符串拼接 +
逻辑 && ||
位 & | ^
一元 + - ! ~
移位 >> >>> <<
关系 == > < >= <=
instanceof
组 ( )
字面量 - 字符,字符串,数字,null
方法调用
字段访问
数组访问 [ ]
三元操作 ? :

表达式语法不支持以下操作:

this

super

new

显式泛型调用<T>

(6)转义字符

简单的用法就写到这了 就不长篇大论了 能坚持看到这的说明还没被我催眠0.0

原理这里也不分析了,太长看起来也累。

有兴趣的可以看我写的《Android ButterKnife入门到放弃》的原理分析,就会发现两者有点类似

七、插件支持

https://plugins.jetbrains.com/plugin/9271-databinding-support
使布局转化更加简单

八、Demo地址

https://github.com/DayorNight/DataBinding

九、参考文档

https://developer.android.google.cn/topic/libraries/data-binding/?hl=zh_cn
https://www.imooc.com/learn/719

十、内容推荐

《CSDN》
《Android ButterKnife入门到放弃》​​​​​​​
《Android 下载安装应用APK封装(适配8.0)》
《Android Notification通知简单封装(适配8.0)​​​​​​​》​​​​​​​
《Android 10文档阅读总结》
若您发现文章中存在错误或不足的地方,希望您能指出!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容