Android官方数据绑定框架DataBinding(二)

六、 表达式 
短暂的幸福时光,我们还是要告别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的时候执行EventHandlershandleClick方法。 
继续看看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,这里有ObservableFieldObservableBoolean,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都会有一对getset方法,所以使用起来也很方便了:

...  
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);

太简单了,简直和ListMap使用方法一模一样!!!
十、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可以获取我们加载的布局,是不是很简单? 
来看一下效果:


你可能感兴趣的:(Android官方数据绑定框架DataBinding(二))