DataBinding入门完整教程

DataBinding,2015年IO大会介绍的一个框架,字面理解即为数据绑定。由于一般的开发过程中,Activity既需要做实现网路请求的代码,又需要实现界面的渲染/用户之间的交互,如果一个页面的功能更为复杂 对后期的项目维护更加艰难。因此,推出该框架有利于简化功能模块 尽量将界面的渲染/用户交互的功能分化在单独的模块中。
一个案例简单入门

举个例子,我们想对某个文本控件设置显示文本,首先要通过findViewById()获取控件再设值;
再举个例子,如果我们想获取某个控件的点击事件,首先要通过findViewById()获取控件在绑定监听器。

如果一个界面有很多控件需要处理,很麻烦是不?现在通过新技术来展示下需要同样的功能如何做:

因为android默认支持DataBinding,只需要在app项目中的gradle配置添加

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

  

在需要绑定数据的xml布局文件中添加layout标签,例如原来的布局界面是这样的

<LinearLayout
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

现在是这样的:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.m520it.databinding.User" />
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"/>
    </LinearLayout>

</layout>

    上面的代码首先在最外一层多了个标签,他用来区分数据模块和布局模块。
    标签相当于声明数据模块,该模块包含导包 声明变量/对象。
    表明声明一个变量,变量名叫user 对象类型是com.m520it.databinding.User
    下面的TextView中文本是@{user.name} 表明我们要取出user对象的name属性。

上面定义了一个User对象,但是还没实例化,我们先看看该对象的类是如何定义的:

public class User {

    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //还记得上面要取出user对象的name属性吗 其实就是调用了getter方法
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

类出来后 我们在Activity中初始化对象 并把对象映射到布局中:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 1.获取<data />标签对象
        ActivityMainBinding mBinding =
                                DataBindingUtil.setContentView(this, R.layout.activity_main);
        // 2.创建User对象
        User mUser=new User("SeeMyGo ",1);
        // 3.绑定到mUser到布局对象中
        mBinding.setUser(mUser);
    }
}

    DataBindingUtil是一个帮助类,可以将一个布局文件转换成一个标签对象。默认返回的是ViewDataBinding对象,这里为什么返回了一个ActivityMainBinding对象,因为我们的布局名称是activity_main.xml。只要布局文件变化了,那么该对象也会跟着变化。
    mBinding对象有一个setUser()的方法。这是因为布局里面有个的属性。系统会自动产生对应的setter方法(系统可能会编译报错 不过是可以运行的)

上面的例子运行后,会在界面中显示SeeMyGo,不过没图没真相 自己测试一次吧!!
通过id无需自己创建控件

我们平时在布局中为每个控件添加一个id,如下的TextView:

<TextView
    android:id="@+id/tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

如果想获取控件,还需要通过findViewById(R.id.xxx);才能找到该控件 这得多浪费时间。

现在你可以这么做.在外面添加一个layout标签,其他不用变:

点击按钮 发现mBinding内部多了一个tv的文本控件对象:

public void myClick2(View v){
    mBinding.tv.setText("测试加载的数据...");
}

减少点击事件的绑定

严格意义上来说,事件绑定也是一种变量绑定。我们可以在xml中直接绑定

android:onClick
android:onLongClick
android:onTextChanged
…

1.在布局中声明对象main,并在按钮点击onClick属性的时候,调用@{main::simpleClick01} 这里就是说要调用对象main的simpleClick01方法。

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

    <data>

        <variable
            name="main"
            type="com.m520it.databinding.MainActivity" />
    </data>
    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点击事件"
            android:onClick="@{main::simpleClick01}" />
    </LinearLayout>
</layout>

2.进入代码区,首先我们要为对象实例化 可以看下onCreate()方法

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    //注意这里千万要绑定具体的值
    mBinding.setMain(this);
}

3.当点击按钮的时候 代码如下:

public class MainActivity extends AppCompatActivity {

    public void simpleClick01(View v) {
            Toast.makeText(MainActivity.this, "我被点到了!", Toast.LENGTH_LONG).show();
        }

}

支持在布局中使用表达式

常用表达式跟Java表达式很像,以下这些是一样的:

    数学 + - / * %
    字符串连接 +
    逻辑 && ||
    二进制 & | ^
    一元运算 + - ! ~
    移位 >> >>> <<
    比较 == > < >= <=
    instanceof
    分组 ()
    null
    Cast
    方法调用
    数据访问 []
    三元运算 ?:

我们可以再任何属性中使用如上的表达式,举个例子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

Null合并操作:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"  

如上的写法太繁琐 可以这样:

    ?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:

    android:text="@{user.displayName ?? user.lastName}"

BaseObservable支持实时刷新界面

上面第一个例子中,user对象绑定了对象后,如果刷新了user对象,界面中调用该对象相对应属性的界面没有被刷新。我们需要user对象刷新,与该对象相对应的界面显示值就跟着刷新,以下例子可以帮我们实现:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;
    private User mUser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        mUser=new User();
        mBinding.setUser(mUser);

    }

    public void myClick(View v){
        mUser.setName("zhangsan");
    }
}

上面的代码中,布局mBinding先绑定对象mUser,点击按钮执行myClick(),修改User的name,你会发现,布局居然没修改,很坑是吧?这里需要介绍一个新的对象BaseObservable。

首先要修改的是User对象,先看看getter方法,所有的getter方法都添加了一个注解@Bindable,它支持该属性在BR类中产生一个对应的静态int类型常量,至于什么是BR,你可以认为它类似于我们认识的R类;另一个变化就是setter方法中多了一句notifyPropertyChanged(BR.xxx);代码,该代码用来通知刷新当前显示的那个布局:

public class User extends BaseObservable{

    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    public void setAge(int age) {
        this.age = age;
        notifyPropertyChanged(BR.age);
    }

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

    @Bindable
    public int getAge() {
        return age;
    }
}

还是上面的示例调用代码,点击按钮 你会发现 界面刷新了。
DataBindingUtil&ViewDataBinding注意点

1.DataBindingUtil一般有两个静态的方法都可以获取布局对象:

    DataBindingUtil.setContentView(this, xxx);
    DataBindingUtil.inflater(layoutInflater,xxx…);

2.上面的对象返回值默认的ViewDataBinding.不过 不同的布局可以返回不同的子类,举个例子,比如我们的布局是 abc_de.xml 那么对应的布局对象是AbcdeDataBinding。不过我们也可以定义该对象的名称,例如:

<data class=".CustomDataBinding">
</data>

上面的例子 返回布局对象名称为CustomDataBinding对象,代码是

CustomDataBinding binding=DataBindingUtil.setContentView(this, xxx);

3.当获取布局对象后,我们就可以为布局中的变量初始化。以下的两种功能是等效的。

<variable
    name="user"
    type="com.m520it.databinding.User" />

//绑定方法1
mBinding.setUser(mUser);

//绑定方法2
binding.setVariable(BR.user, mUser);

支持集合

除了支持对象的绑定 还支持各种集合容器,常用的集合:array、list、sparse list以及map,为了简便都可以使用[]来访问。这里以一个List示例为准:

1.布局文件如下:

    import标签 就是导包的意思 除了java.lang.*的包和8大基本类型不用导 其他的都需要
    ArrayList<String>的意思本来是ArrayList 因为在布局文件中这2个字符比较特殊 所以需要使用转义字符

    @{list[index]}说明要简单获取列表第index个数据。   

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

        <data>
            <import type="java.util.ArrayList" />

            <variable
                name="list"
                type="ArrayList&lt;String&gt;" />

            <variable
                name="index"
                type="int" />
        </data>

        <RelativeLayout
            android:id="@+id/activity_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{list[index]}" />
        </RelativeLayout>
    </layout>


 

2.代码绑定数据

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding =
                DataBindingUtil.setContentView(this, R.layout.activity_main);
        ArrayList<String> datas=new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            datas.add("SeeMyGo "+i);
        }
        //以下代码绑定了2个数据
        binding.setVariable(com.m520it.simplecollection.BR.list,datas);
        binding.setVariable(com.m520it.simplecollection.BR.index,2);
    }
}


 

include布局共享数据

在布局开发中,我们经常会include一个子布局,如果内部的布局与外部的布局使用共同的绑定对象,使用步骤如下:

1.创建外层的布局

<?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.m520it.databinding.User" />
    </data>
    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <include
            layout="@layout/include_layout"
            bind:user="@{user}" />
    </LinearLayout>
</layout>


    20

2.这里包含了另外一个include_layout布局,代码如下:

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

    <data>
        <variable
            name="user"
            type="com.m520it.databinding.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

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

3.上面的外层代码出现了bind:user=”@{user}”,记住这里的命名空间需要我们再次定义。这里的意思是外层的user变量设置成功后 包含的内部布局也跟着使用同样的数据。
对ViewStub的支持

ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。

可以为ViewStub指定一个布局,在调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。这样,就可以使用ViewStub来方便的在运行时,要还是不要显示某个布局。

首先布局添加如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        ...
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="加载3"
            android:onClick="myClick3" />
        <ViewStub
            android:id="@+id/viewstub"
            android:layout="@layout/stub_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>

这里,stub_layout的布局如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user2"
            type="com.m520it.databinding.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user2.name}" />
    </LinearLayout>
</layout>

代码调用如下:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;
    private User mUser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //在viewstub初始化的时候调用如下监听器
        mBinding.viewstub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                StubLayoutBinding binding=DataBindingUtil.bind(inflated);
                binding.setUser2(new User("lisi",44));
            }
        });
    }

    public void myClick3(View v){
        mBinding.viewstub.getViewStub().inflate();
    }
}

上面的例子中,点击按钮可以看到viewstub里面的文本控件赋值成功并看到界面中显示lisi。
完全替换ListView的Holder对象

如果你觉得上面的内容没啥实用 可以看看这章内容。

1.初始化ListView控件

ListView listView = (ListView) findViewById(R.id.listview);
MyAdapter myAdapter = new MyAdapter(this);
listView.setAdapter(myAdapter);

2.使用ViewDataBinding替换ViewHolder

public class MyAdapter extends BaseAdapter {

    //在Adapter创建的时候初始化列表数据
    private final LayoutInflater mInflater;
    private ArrayList<Person> mDatas = new ArrayList<>();

    public MyAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
        for (int i = 0; i < 30; i++) {
            mDatas.add(new Person("san", "zhang"));
        }
    }

    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //这里使用ViewDataBinding替换掉了ViewHolder
        ViewDataBinding binding = null;
        if (convertView == null) {
            binding = DataBindingUtil
                    .inflate(mInflater, R.layout.list_item_layout, parent, false);
            //binding.getRoot()其实就是取出list_item_layout布局对应的View对象
            convertView = binding.getRoot();
            convertView.setTag(binding);
        } else {
            binding = (ViewDataBinding) convertView.getTag();
        }
        //从mDatas取出某个item数据,并将整个对象设置给布局中的占位符
        binding.setVariable(com.m520it.listviewwithdatabinding.BR.person, mDatas.get(position));
        return convertView;
    }
}

3.在子项布局通过数据的属性绑定对应的控件

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

    <data>
        <variable
            name="person"
            type="com.m520it.listviewwithdatabinding.Person" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{person.firstName}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="50dp"
            android:text="@{person.lastName}" />
    </LinearLayout>

</layout>

4.上面的Person对象 其类的定义为:

public class Person {

    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}

对RecyclerView子项的支持

以下内容假设用户已经学会RecyclerView相关知识点了。

1.创建RecyclerView布局

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2.获取控件并绑定适配器RecyclerAdapter。

RecyclerView recyclerView = (RecyclerView)  findViewById(R.id.recycler_view);
//设置RecyclerView的布局策略为线性布局
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//设置RecyclerView的item动画
recyclerView.setItemAnimator(new DefaultItemAnimator());
//设置适配器
RecyclerAdapter recyclerAdapter = new RecyclerAdapter();
recyclerView.setAdapter(recyclerAdapter);

3.下面RecyclerAdapter才是重点,整个过程完全忽视了每个item布局子控件的绑定并设置数据。

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

//在adapter创建的时候 初始化模拟的数据
private ArrayList<Person> mDatas = new ArrayList<>();

public RecyclerAdapter() {
    for (int i = 0; i < 20; i++) {
        mDatas.add(new Person("zhangsan"+i,i+""));
    }
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    ViewDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.list_item_layout, parent, false);
    ViewHolder viewHolder = new ViewHolder(binding.getRoot());
    //将binding绑定到ViewHolder中
    viewHolder.setBinding(binding);
    return viewHolder;
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    //取出ViewHolder中的binding对象 并通过binding对象绑定数据
    holder.getBinding().setVariable(BR.person,mDatas.get(position));
}

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

//看见没 这是变化最大的模块 你完全看不到item布局中的所有子控件的初始化,只有一个ViewDataBinding并设置setter getter方法。
class ViewHolder extends RecyclerView.ViewHolder {

    private ViewDataBinding binding;

    public ViewDataBinding getBinding() {
        return binding;
    }

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

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

}

4.当然 系统肯定还是需要我们去告诉它值设置在哪里的。以下的布局为item布局

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

    <data>
        <variable
            name="person"
            type="com.m520it.recycleviewwithdatabinding.Person" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{person.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{person.age}" />
    </LinearLayout>
</layout>

---------------------
作者:CoderLean
来源:CSDN
原文:https://blog.csdn.net/qq285016127/article/details/54835731

猜你喜欢

转载自blog.csdn.net/b1480521874/article/details/88263963