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<String>" />
<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