在这里,实现android 的数据绑定,是依靠Data Binding library库实现的。目的是将数据的交互由activity移动到layout布局文件和专门的数据类中,从而做到更好的封装。初学jetpack包,其实是抱着偷懒的心理来的。一开始感觉,这个databing 与 srping boot 当中mybbits很是想像,解决的问题也是数据展示与数据处理分离。想让布局的数据随着数据对象的变化自动变化。
现在就文档上介绍的三种方法来交流下个人在使用的心得。其实这三种数据绑定方法,分别是由浅入深的,数据类型从刚开始的初始类型,到可观察的初始类型,再可观察的初始类型及其容器,最后是整个可观察的类,实现自行更新ui,数据逻辑。开始走起!
一、最简单形式
layout —— 数据类 —— activity
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
其中表达式是@{}, 数据是在data标签内,并表明了数据variable变量的名字,你可以下方面的控件中引用对象的名字,类型,你定义的数据类。在data标签里还可以import,导入资源,像这样。
<import type="com.example.android.databinding.basicsample.R"/>
<import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
<variable
name="user"
type="com.example.android.databinding.basicsample.data.ObservableFieldProfile" />
这样,你声明的variable,和import来的资源,软件包,都可以在下面的布局控件中在@{}表达中使用,还有它也有一个default可以让你指定默认值, 这个要用``包住,数字键1前面的那个键。表示是个常量。
数据类:
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
final修饰符表示,这个类型数据,程序只能用读,数据不变的。
activity这样的在onCreate方法中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}
而
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
是从运行是获那个绑定对象。ActivityMainBinding 这是一个根据layout文件名称生成的生成类,以Binding为后缀。
这三者里面,在layout文件中要使用的表达式,很强大,支持下下运算,见识下:
一类跟java代码中相似的运算符:
Mathematical + - / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + - ! ~
Shift >> >>> <<
Comparison == > < >= <= (Note that < needs to be escaped as <)
instanceof
Grouping ()
Literals - character, String, numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:
而不能使用类似java中的this, supper, new, 或直接代码调用。
支持??空联合运算 ,可以看作?:的缩写,不为空选择前面,空时选择后面值。
android:text="@{user.displayName ?? user.lastName}"
还支持,对象属性引用,只是注意空指针引用,那会引发异常。
android:text="@{user.lastName}"
更支持,集合运算[]
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
map[key]当然也可以用map.key来代替。
注意,如果双引号要用字符串字面量,则要用键盘数字1前面那个符号,叫后引号。又或者字面量用双引号,而外层用单引号。
表达内,还可以引用资源:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
支持还支持string.xml里面格式化字符串,像这样
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
你需要传参数:
android:text="@{@string/welcome_messages(firstName, 50)}"
还少用的字符串复数形式:
<plurals name="numberOfSongsAvailable">
<!--
As a developer, you should always supply "one" and "other"
strings. Your translators will know which strings are actually
needed for their language. Always include %d in "one" because
translators will need to use %d for languages where "one"
doesn't mean 1 (as explained above).
-->
<item quantity="one">%d song found.</item>
<item quantity="other">%d songs found.</item>
</plurals>
也记要传参.
还有一些资源引用时,表达式内要明确求值格式,这些是:
Type | Normal reference | Expression reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
以是在表示式里求值,赋给控件属性,控件的属性中除了值,还有一种是事件处理器。当控件的事件被触发后,要调用相应的处理代码,这个我们提供并设定。databing 库为我们提供了两方法来实现,方法引用和事件绑定。
一是通过方法引用。我们不仅可以将数据对象绑定到layout当中,还可以将一个用于处理事件的类,也放到layout的data标签中去。然后在控件的响应事件处引用我们处理事件类中的方法。比如:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
许多控件都onClick,控件的事件也会传递,注意引起冲突的地方。其中一些特别的如:
Class | Listener setter | Attribute | |
---|---|---|---|
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn | |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut | |
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick | |
第二方法,处理方法,绑定事件侦听器。如示:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
需要我们定义那个侦听器:
public class Presenter {
public void onSaveClick(Task task){}
}
当表达中有了这么一个回调监听器的指定,数据绑定模块会自动产生侦听器实例,并把它注册到事件流当中来。这里侦听器,可以给它传参,也可以不传,还可以确定返回值。不过,值得注意的是避免空值。
如果你需要一个带指示的表达式,可以这样:
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
尽管表达式地很强大,但不要滥用,使用代码阅读困难,应该很简单,只像是从ui到从到你的回调,你应该实现回调代码内的业务逻辑,以便表达内用到它实现相应的功能。
最后,再补充下import 和 include,它们有效地扩展了数据的能力范围。
import有点像代码的中导入到当前空间一样,只是为了避免名字冲突你还可以使用别名,如:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
除了导入系统的类,也可以是自己的类,并可以作为引用数据的数据类型,如:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
引入地类可以作为强型转换,也可有表达式内引用它的静态成员。作为管理代码的一部分,java.lang.*是自动导入的。
在data标签中的变量variable可以有多个条目,要标明name,type,如:
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
注意此处类型会编译时侦测,类型是实现了Observable或
Observable collection的类型,还要注意不同的layout配置文件相同变量名合并时冲突问题,都是要避免的。根据layout文件自动生成的绑定类,对变量有一对setter,getter有其缺省值,引用型为null,int为0, boolean为false等,但一个很特别的变量名,context,它是从根View的getContext()方法中得到Context对象,它会覆盖你明确定义的context.
接下来是include,将别处layout引用变量的表达式,引入到当前声明变量的layout当中。如:
<?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.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
不支持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.example.User"/>
</data>
<merge><!-- Doesn't work -->
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
总结:这里首先是介绍了简单数据绑定,重点是表达使用,引入,导入。特点是表达式内容较大。