Android DataBinding使用指北

2018-03-01 11:17:48来源:https://www.jianshu.com/p/c9a0b0c53524作者:许渺人点击

分享


DataBinding(数据绑定库)是Google提供的一个支持库,能够帮助我们减少应用程序和布局之间的一些冗余代码,比如我们以前常写的findViewById,不仅如此;我们所说的MVVM模式也是基于它来实现的,下面让我们来了解它的更多好处吧。


一. 构建环境


注意:DataBinding只能往下兼容到Android 2.1(API 7+),Android Studio的Gradle插件版本必须高于1.5.0-alpha1,并且Android Studio v1.3+才能使用.



在module的build.gradle中开启dataBinding


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

二. DataBinding简单体验

我们先来定义一个java bean对象,很普通的一个User类


public class User
{
private String name;
private int age;
public User(String name, int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
@Override
public String toString()
{
return "User{" +
"name='" + name + '/'' +
", age=" + age +
'}';
}
}

接来下编写我们的布局,DataBinding布局文件和普通布局文件有些不同,它是以<layout>为根布局,后面跟着<data>元素紧跟着是view的根布局。


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.xuyj.databinding.sample.User"/>
</data>
<LinearLayout
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xuyj.databinding.sample.BasicUsageActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.toString()}"
android:textColor="@color/colorBlack"
android:textSize="16sp"/>
</LinearLayout>
</layout>

最后我们编写Activity的代码。


public class BasicUsageActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ActivityBasicUsageBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_basic_usage);
User user = new User("许渺", 18);
binding.setUser(user);
}
}

Activity的代码很简单,关于布局里面的TextView我们并没有使用findViewById来setText,有木有瞬间感觉代码简洁了不少?




.
三. 布局分析

上面的代码我们只是粗略的了解了DataBinding,接下来让我们从布局文件入手,正式开始学习DataBinding。


<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.xuyj.databinding.sample.User"/>
</data>
</layout>

首先和普通布局不同的是,根节点变成了layout,它分为两部分;一部分是data元素,另一部分是我们的视图元素;data下我们定义了variable(代表变量),name代表变量名,type是数据的类型(全路径)。
变量定义好了,接下来就是使用了。


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.toString()}"
android:textColor="@color/colorBlack"
android:textSize="16sp"/>

使用”@{}“语法来设置值,这里我们设置了user对象toString返回的数据。


五. 事件处理

Data Binding允许我们编写表达式来处理View分发的事件(如onClick),事件属性名称由监听器方法进行管理,例如View.OnLongClickListener有一个方法onLongClick,所以这个属性的是android:onLongClick,处理事件有两种方法:


方法引用

定义监听方法,这个和直接在布局文件中写onClick=“method”类似,这里就不多赘述了。


public class Presenter
{
public void onClick(View view){...}
}

在布局中添加点击事件表达式


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="presenter"
type="com.xuyj.databinding.sample.BasicUsageActivity.Presenter"/>
</data>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{presenter::onClick}"
android:text="点我^_^(Method)"/>
</LinearLayout>
</layout>

监听器绑定

它与方法引用类似,但是它允许运行任意数据绑定表达式,并且表达式采用的是lambda表达式;需要注意的是它只适用于Gradle 2.0+版本的Android Gradle插件上(现在应该不会有人的AS低于它吧...),所以相对来说还是比方法引用更加灵活。


还有,布局中表达式的参数必须要和事件监听器的参数匹配,比如
onCheckedChanged(CompoundButton buttonView, boolean isChecked);


public class Presenter
{
public void completeChanged(boolean isChecked){...}
}

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="presenter"
type="com.xuyj.databinding.sample.BasicUsageActivity.Presenter"/>
</data>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(view,isChecked) -> presenter.completeChanged(isChecked)}"
android:text="点我$_$(Listener)"/>
</LinearLayout>
</layout>

布局细节
Import

可以帮助我们导入布局文件中的类,和java中的import是一样的。


<data>
<import type="com.xuyj.databinding.sample.User"/>
<import type="android.view.View"/>
<variable name="user" type="User"/>
</data>

使用表达式
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.age > 20 ? View.VISIBLE : View.GONE}"/>

当不同包下类名相同时,我们可以定义别名,如下


<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>

注意:java.lang.*下的类是自动导包的


自定义Binding类名

默认情况下,系统会根据布局文件的名称生成一个Binding类,以大写字母开头,以Binding后缀结尾,给类放在databinding包下,例如,布局文件activity_basic_usage,会生成ActivityBasicUsageBinding如果我们的包名是com.xuyj.databinding.sample,那么它会放在com.xuyj.databinding.sample.databinding下。


我们可以调整data元素的class属性(修改类名),例如


<data>
...
</data>

如果要类在当前包下,只要在前面加”.“就可以了


<data>
...
</data>

自定义包名


<data>
...
</data>




Includes

变量可以从布局中传递到include包含的布局中


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.xuyj.databinding.sample.User"/>
</data>
<LinearLayout
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xuyj.databinding.sample.BasicUsageActivity">
<include
layout="@layout/name"
bind:name="@{user.name}"/>
</LinearLayout>
</layout>

在这里,include的布局文件也要有对应的变量,name.xml如下:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="name"
type="String"/>
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{name}"/>
</LinearLayout>
</layout>

Data Binding不支持include直接作为merge直接的子元素,例如


<merge>
<include
layout="@layout/name"
bind:name="@{user.name}"/>
</merge>

表达式语言

表达式语言看起来和java是一样的,如下:


数学 + - / * %
字符串拼接 +
逻辑操作符 && ||
二进制 & | ^
一元符 + - ! ~
位移 >> >>> <<
比较符 == > < >= <=
instanceof
Grouping ()
字符 - character, String, numeric, null
Cast类型转换
方法调用
属性访问
数组访问 []
三元操作符 ?:
不支持的表达式
this
super
new
Explicit generic invocation(比如泛型类)
空合并运算符

空合并运算符(?),如果左边为空,则选择右边,反之亦然。


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

其实上面的就等同于


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

避免NullPointerException

生成的数据绑定代码会自动检查空值并避免空指针异常。例如,在表达式中@{user.name},如果user为null,将会为它分配默认值(null),如果是int类型,默认值为0


集合

数组,List,Map,SparseArray,都可以通过[]来进行访问。


<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List&lt;String&gt;"/>
<variable name="sparse" type="SparseArray&lt;String&gt;"/>
<variable name="map" type="Map&lt;String, String&gt;"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>

android:text="@{list[index]}"

android:text="@{sparse[index]}"

android:text="@{map[key]}"

六. 数据对象

任何普通的java bean都可以用于数据绑定,但是修改java bean不会导致UI更新,为了解决这个问题,Data Binding提供了三种不同的数据更改通知机制;可观察对象、可观察字段和可观察集合


Observable Objects

定义类继承BaseObservable,在getter方法上加上@Bindable,在setter方法中调用notifyPropertyChanged方法,这里BR是记录布局文件中定义的变量,和R文件有点类似。


public class User extends BaseObservable
{
private String name;
private int age;
public User(String name, int age)
{
this.name = name;
this.age = age;
}
@Bindable
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
// notifyPropertyChanged(BR.age);
}
@Override
public String toString()
{
return "User{" +
"name='" + name + '/'' +
", age=" + age +
'}';
}
}

ObservableFields

如果你嫌第一种方式麻烦,那你可以考虑换一种口味;Google工程师封装了以下这些类;ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable,用法也很简单,如下


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

接下来该怎么访问呢?也很简单,通过getter,setter来访问,so easy!


user.name.set("许渺");
int age = user.age.get();

Observable Collections

既然有了变量,集合当然也不能少啊;Android提供了ObservableArrayMapObservableArrayList,用法和java是一样的,我们以ObservableArrayMap来举例。


ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("name", "许渺");
user.put("age", 18);

布局文件中也是一样的套路,先定义变量绑定数据,如下:


<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>

<TextView
android:text='@{user["name"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

七. 带有id的控件如何访问呢?

这个和kotlin的插件很像,非常方便;这个时候就可以丢掉你的Butterknife了!


<TextView
android:id="@+id/user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.toString()}"
android:textColor="@color/colorBlack"
android:textSize="16sp"/>

那么在Activity中如果访问呢?我已经猜出来了。


ActivityBasicUsageBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_basic_usage);
binding.user.setText("zz");

这里要注意的是布局中的命名,如果你使用了下划线“_”的话,那么下划线后面的字母会自动变成大写,这是它的规则!


八. ViewStubs

ViewStub比较特殊,我们单独抽出来说一下,它在调用inflate之前是不可见的,DataBinding使用了ViewStubProxy这个类来实现,我们需要设置监听,当布局填充的时候将会调用onInflate方法




.

这边布局代码就不贴了,我们直接来看一下activity的实现,具体代码可以看下面的源码


public void load(View view)
{
mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener()
{
@Override
public void onInflate(ViewStub stub, View inflated)
{
mBinding.setName("啦啦啦");
}
});
mBinding.viewStub.getViewStub().inflate();
}

九. 创建ViewBinding

之前,我们在Activity中都是通过DataBindingUtil.setContentView(activity,layoutId)来加载布局的,那么问题来了;如果是Fragment或者是RecyclerView中该怎么办呢?不用担心,DataBinding提供了其它的方法来实现(下面两个是长常用的)。


MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

Fragment实践
public class TestFragment extends Fragment
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
FragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_test, container, false);
binding.setUser(new User("许渺z", 20));
return binding.getRoot();
}
}

RecyclerView实践
public class ListActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(new ListAdapter(this));
}

private class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder>
{
private List<User> mListData;
private final LayoutInflater mInflater;
public ListAdapter(Context context)
{
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mListData = new ArrayList<>();
for (int i = 0; i < 10; i++)
{
mListData.add(new User("许渺" + i, i + 10));
}
}
@Override
public ListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
ItemListBinding binding = DataBindingUtil.inflate(mInflater, R.layout.item_list, parent, false);
ViewHolder holder = new ViewHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(ListAdapter.ViewHolder holder, int position)
{
User user = mListData.get(position);
holder.binding.setVariable(BR.user, user);
holder.binding.executePendingBindings();
}
@Override
public int getItemCount()
{
return mListData.size();
}
class ViewHolder extends RecyclerView.ViewHolder
{
ViewDataBinding binding;
public ViewHolder(View itemView)
{
super(itemView);
}
public ViewDataBinding getBinding()
{
return binding;
}
public void setBinding(ViewDataBinding binding)
{
this.binding = binding;
}
}
}
}

executePendingBindings() 这个方法是当变量发生改变时,调用该方法进行强制刷新,嗯!就是这样。


这边运行效果图就不贴了,毕竟界面长得比较丑。




.
十. 高级绑定
自定义setter

假如我们要在ImageView中指定网络图片URL路径来进行显示,显然是不太可能的,但是Data Binding提供了@BindingAdapter注解来帮我们实现,它的参数是一个数组,我们先来看看怎么使用!


<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="imageUrl"
type="String"/>
</data>

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:imageUrl="@{imageUrl}"/>
</layout>

上面我们自定义了一个imageUrl属性来指定图片的Url路径,下面Activity,就一句代码,给它设置值。


public class CustomSetterActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
CustomSetterBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_custom_setter);
binding.setImageUrl("/2014th7cj/d/file/p/20180228/fd5plsah5jx.jpg");
}
}

接下来是最重要的,加载图片这里使用的是Glide(你可以选择自己喜欢的框架),首先我们随便定义一个类;你可以把它当做是工具类。


public class Utils
{
// @BindingAdapter({"bind:imageUrl"})
// 官网文档是上面这样写的,但是编译的时候会有个警告,所以按下面这样写吧...
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView imageView, String url)
{
Glide.with(imageView.getContext()).load(url).into(imageView);
}
}

下面是运行结果,长这样...




.
自定义Converters

转换的意思,比如我们要给TextView设置格式化的时间,但是你只有Date变量肿么办?虽然你可以先转换后在进行设置,但是Data Binding也提供了另一个方案,@BindingConversion你没有看错,它也是一个注解;我们先来看看它怎么使用吧!


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="date"
type="java.util.Date"/>
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{date}"/>
</layout>

public class ConvertersActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ConvertersBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_converters);
binding.setDate(new Date());
}
}

接下来便是时间的转换了,还是随便定义一个工具类,和上面自定义setter一样。


public class Utils
{
@BindingConversion
public static String converterDate(Date date)
{
return new SimpleDateFormat("yyyy-HH-mm hh:MM:ss").format(date);
}
}

下面是Converter ViewBinding中的代码,它是这样来转换的,setter也一样,所以我说它其实就是个工具类。




.
最后

关于Data Binding我还没有运用在项目中,这篇博客写的也有点乱;欢迎各位小伙伴及时指正错误,感谢!


Github源码点这里
参考

https://developer.android.google.cn/topic/libraries/data-binding/index.html#converters


http://blog.csdn.net/jdsjlzx/article/details/48133293








最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台