六、 表达式
短暂的幸福时光,我们还是要告别java代码了,继续回到xml中,这一块,我们来学习一下表达式,什么?这玩意在xml中还支持表达式!
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{error ? "error" : "ok"}'/>
还记得上面我们定义了一个boolean的变量没有用到,这里我们就用到了,看好android:text
,这里是一个三元表达式,如果error是true,则text就是error,否则是ok。这里还支持null合并操作,什么是null合并,相信看一眼你就知道了
android:text='@{str==null ?? "not null"}'
简单解释一下,如果str是null,text的值就是str本身,否则就是”not null”。
它还支持一下表达式:
Mathematical + - / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + - ! ~
Shift >> >>> <<
Comparison == > < >= <=
instanceof
Grouping ()
Literals - character, String, numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:
但是它不支持一下表达式:
this
super
new
Explicit generic invocation
七、 其他遗漏点
说到这里,xml中的事情基本算完了,但是还有几个小地方没有说,顺便说一下。
1. 设置别名
假如我们import了两个相同名称的类咋办?别怕,别名来拯救你!例如:
... <data> <import type="xxx.Name" alias="MyName"> <import type="xxx.xx.Name"> </data> <TextView xxx:@{MyName.getName()}> <TextView xxx:@{Name.getName()}> ...
自定义Binding名称
还记得系统为我们生成好的那个binding类名吗?如果只能使用那样的是不是有点太暴力了?好在google对我们还算友好了,允许我们自定义binding名称,定制名称也很简单,就是给data一个class字段就ok。
例如:
<data class=".Custom">...</data>
那么:DataBindingUtils.setContentView返回的binding类就是:你的应用包名.Custom
八、事件绑定
大家都知道,在xml中我们可以给button
设置一个onClick
来达到事件的绑定,现在DataBinding也提供了事件绑定,而且不仅仅是button
。
来看一下:
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="org.loader.app3.EventHandlers" /> <variable name="handlers" type="EventHandlers" /> </data> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CLICK ME" android:onClick="@{handlers.handleClick}"/> </LinearLayout> </layout>
定义了一个EventHandlers
类型的handlers
变量,并在onClick的时候执行EventHandlers
的handleClick
方法。
继续看看EventHandlers是怎么写的。
public class EventHandlers { public void handleClick(View view) { Toast.makeText(view.getContext(), "you clicked the view", Toast.LENGTH_LONG).show(); } }
很简单,就是简单的Toast
了一下,这里要注意的是,handlerClick
方法需要一个View
的参数。
九、 数据对象
我们学会了通过binding为我们的变量设置数据,但是不知道你有没有发现一个问题,当我们数据改变的时候会怎样?数据是跟随着改变呢?还是原来的数据 呢?这里告诉你:很不幸,显示的还是原来的数据?那有没有办法让数据源发生变化后显示的数据也随之发生变化?先来想想ListView
是怎么做的, ListView
的数据是通过Adapter
提供的,当数据发生改变时,我们通过notifyDatasetChanged
通过UI去改变数据,这里面的原理其实就是内容观察者,庆幸的是DataBinding也支持内容观察者,而且使用起来也相当方便!
BaseObservable
我们可以通过Observable的方式去通知UI数据已经改变了,当然了,官方为我们提供了更加简便的方式BaseObservable
,我们的实体类只需要继承该类,稍做几个操作,就能轻松实现数据变化的通知。如何使用呢? 首先我们的实体类要继承BaseObservale
类,第二步在Getter
上使用注解@Bindable
,第三步,在Setter
里调用方法notifyPropertyChanged
,第四步,完成。就是这么简单,下面我们来实际操作一下。
首先定义一个实体类,并继承BaseObservable
public class Student extends BaseObservable { private String name; public Student() { } public Student(String name) { this.name = name; } @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(org.loader.app4.BR.name); } }
观察getName方法,我们使用了@Bindable
注解,观察setName,我们调用了notifyPropertyChanged
方法,这个方法还需要一个参数,这里参数类似于R.java
,保存了我们所有变量的引用地址,这里我们使用了name。
再来看看布局文件。
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data class=".Custom"> <import type="org.loader.app4.Student" /> <variable name="stu" type="Student"/> <variable name="click" type="org.loader.app4.MainActivity" /> </data> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{click.click}" android:text="@{stu.name}"/> </layout>
不多说了,我们给TextView
设置了文本,还有点击事件。Activity,
public class MainActivity extends AppCompatActivity { private Student mStu; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); org.loader.app4.Custom binding = DataBindingUtil.setContentView(this, R.layout.activity_main); mStu = new Student("loader"); binding.setStu(mStu); binding.setClick(this); } public void click(View view) { mStu.setName("qibin"); } }
这段代码,首先显示的是loader,当我们点击TextView
时,界面换成qibin。
ObservableFields家族
上面使用BaseObservable
已经非常容易了,但是google工程师还不满足,继续给我们封装了一系列的ObservableFields
,这里有ObservableField
,ObservableBoolean
,ObservableByte
,ObservableChar
,ObservableShort
,ObservableInt
,ObservableLong
,ObservableFloat
,ObservableDouble
,ObservableParcelable
ObservableFields的使用方法就更加简单了,例如下面代码,
public class People { public ObservableField<String> name = new ObservableField<>(); public ObservableInt age = new ObservableInt(); public ObservableBoolean isMan = new ObservableBoolean(); }
很简单,只有三个ObservableField变量,并且没有getter和setter,因为我们不需要getter和setter。
在xml中怎么使用呢?
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data class=".Custom"> <variable name="people" type="org.loader.app4.People" /> </data> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{people.name}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(people.age)}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{people.isMan ? "man" : "women"}'/> </LinearLayout> </layout>
也很简单,直接使用变量,那怎么赋值和取值呢?这些ObservableField都会有一对get
和set
方法,所以使用起来也很方便了:
... mPeople = new People(); binding.setPeople(mPeople); mPeople.name.set("people"); mPeople.age.set(19); mPeople.isMan.set(true); ...
也不多说了。
Observable Collections
既然普通的变量我们有了ObservableFields的分装,那集合呢?当然也有啦,来看着两个:ObservableArrayMap
,ObservableArrayList
。使用和普通的Map、List基本相同,直接看代码:
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data class=".Custom"> <variable name="map" type="android.databinding.ObservableArrayMap<String,String>" /> <variable name="list" type="android.databinding.ObservableArrayList<String>" /> </data> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{map[`name`]}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{list[0]}"/> </LinearLayout> </layout>
在布局中,使用方式和普通的集合一样,如果看不太懂,可以往上翻博客,看上面的集合是怎么使用的。
在来看java文件,怎么设置数据,
ObservableArrayMap<String, String> map = new ObservableArrayMap<>(); ObservableArrayList<String> list = new ObservableArrayList<>(); map.put("name", "loader or qibin"); list.add("loader!!!"); binding.setMap(map); binding.setList(list);
太简单了,简直和List
、Map
使用方法一模一样!!!
十、inflate
不知道大家注意没有,上面的代码我们都是在activity中通过DataBindingUtil.setContentView
来加载的布局的,现在有个问题了,如果我们是在Fragment
中使用呢?Fragment
没有setContentView
怎么办?不要着急,Data Binding也提供了inflate
的支持!
使用方法如下,大家肯定会觉得非常眼熟。
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater); MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
接下来,我们就尝试着在Fragment
中使用一下Data Binding吧。
首先还是那个学生类,Student
public class Student extends BaseObservable { private String name; private int age; public Student() { } public Student(int age, String name) { this.age = age; this.name = name; } @Bindable public int getAge() { return age; } public void setAge(int age) { this.age = age; notifyPropertyChanged(org.loader.app5.BR.age); } @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(org.loader.app5.BR.name); } }
继续,activity的布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <FrameLayout android:id="@+id/container" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>
activity的代码,
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getSupportFragmentManager().beginTransaction() .replace(R.id.container, new MyFragment()).commit(); } }
重点来了,我们这里data binding的操作都放在了fragment里,那么我们先来看看fragment的布局。
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data class=".Custom"> <import type="org.loader.app5.Student" /> <variable name="stu" type="Student" /> <variable name="frag" type="org.loader.app5.MyFragment" /> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{frag.click}" android:text="@{stu.name}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(stu.age)}"/> </LinearLayout> </layout>
两个TextView分别绑定了Student的name和age字段,而且给name添加了一个点击事件,点击后会调用Fragment的click方法。我们来迫不及待的看一下Fragment怎么写:
public class MyFragment extends Fragment { private Student mStu; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { org.loader.app5.Custom binding = DataBindingUtil.inflate(inflater, R.layout.frag_layout, container, false); mStu = new Student(20, "loader"); binding.setStu(mStu); binding.setFrag(this); return binding.getRoot(); } public void click(View view) { mStu.setName("qibin"); mStu.setAge(18); } }
在onCreateView
中, 不同于在Activity中,这里我们使用了DataBindingUtil.inflate方法,接受4个参数,第一个参数是一个 LayoutInflater对象,正好,我们这里可以使用onCreateView的第一个参数,第二个参数是我们的布局文件,第三个参数是一个 ViewGroup,第四个参数是一个boolean类型的,和在LayoutInflater.inflate
一样,后两个参数决定了是否想container
中添加我们加载进来的布局。
下面的代码和我们之前写的并无差别,但是有一点,onCreateView
方法需要返回一个View对象,我们从哪获取呢?ViewDataBinding
有一个方法getRoot
可以获取我们加载的布局,是不是很简单?
来看一下效果: