通过Data Binding,通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的模板代码。
在对应的 Module 的 build.gradle 中添加:
buildFeatures {
dataBinding = true
}
如果要用kotlin 开发,还需要添加
apply plugin: 'kotlin-kapt'
// dependencies
kapt "com.android.databinding:compiler:$rootProject.ext.gradle_version"
public class Animal {
private String name;
private String area;
public Animal(String name, String area) {
this.name = name;
this.area = area;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.xf.databindingdemo.Animal"/>
<variable
name="animal"
type="Animal" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:text="@{animal.name}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:text="@{animal.area}"
android:layout_height="wrap_content"/>
LinearLayout>
layout>
根节点由某一个容器(比如LinearLayout)变为layout ,layout中包含了data节点和传统的视图,在data中定义了variable节点。其中,name属性表示变量的名称,type表示这个变量的类型。variable节点的每一个变量都会在Binding辅助类(ActivityExampleBinding.java)中生成对应的getters和setters。接着将@{animal.name}和@{animal.area}赋值给TextView 的text 属性。
public class ExampleActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注释1
ActivityExampleBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_example);
// 注释2
Animal animal=new Animal("巨松鼠","西南部");
binding.setAnimal(animal);
}
}
上面的代码注释1处的代码替换setContentView(),注释2处的代码用来给布局中的控件进行赋值。其中ActivityExampleBinding是系统根据activity_example.xml 生成的一个ViewModel类,它包含了布局文件中所有的绑定关系,并会根据绑定表达式给布局文件赋值。
DataBinding 对点击事件处理,有两种方式:
// xml 文件
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮" />
// 代码
binding.btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(ExampleActivity.this,"点击",Toast.LENGTH_SHORT).show();
}
});
// xml 文件
<import type="android.view.View.OnClickListener"/>
<variable
name="Onclicklistener"
type="OnClickListener" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{Onclicklistener}"
android:text="按钮" />
// 代码
binding.setOnclicklistener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(ExampleActivity.this,"点击",Toast.LENGTH_SHORT).show();
}
});
// xml
<variable
name="name"
type="String" />
<variable
name="age"
type="int" />
<import type="java.util.ArrayList" />
<variable
name="list"
type="ArrayList<String>" />
<import type="java.util.Map" />
<variable
name="map"
type="Map<String,String>" />
<variable
name="arrays"
type="String[]" />
...
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{String.valueOf(age)+"周期"}' />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list.get(1)}" />
<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="@{arrays[2]}" />
为了支持泛型,可以使用转义字符。List集合通过lis[index]来获取数值,也可以调用list.get(index);同样,Map集合也是如此。
// 代码
binding.setName("巨松鼠");
binding.setAge(2);
ArrayList<String> animals=new ArrayList<>();
animals.add("中华鬣羚");
animals.add("猕猴");
animals.add("白枕鹤");
binding.setList(animals);
String[] animalsArray={"白琵鹭","白头鹤","金雕"};
binding.setArrays(animalsArray);
Map<String,String> animalMap=new HashMap<>();
animalMap.put("name","中华鬣羚");
binding.setMap(animalMap);
使用DataBinding提供的Converter,把数据格式转为需要的格式。
// xml
<variable
name="time"
type="java.util.Date" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{time}" />
TextView 的text属性需要的是String类型的值,这里给了一个Date 类型的值,因此,需要写一个Converter 来进行类型转换。
public class BindingUtils {
@BindingConversion
public static String converDate(Date date){
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
return format.format(date);
}
}
// Activity
binding.setTime(new Date());
converDate 方法在哪个类中并不重要,重要的是@BindingConversion注解。converDate方法会将Date类型转换为String类型的日期并返回。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user"
type="com.xf.databindingdemo.Human" />
data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text='@{user.name,default="小米"}'
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{user.age+"",default="18"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
layout>
<variable
name="user"
type="com.xf.databindingdemo.Human" />
...
<include layout="@layout/item_layout"
app:user="@{user}"/>
注意: app:user="@{user}"。第一个user是include里name的引用。第二user是当前传入的值。
DateBinding 提供了3种动态更新机制,根据Model 实体类的内容来动态更新UI,分别对应于类(Observable)、字段(ObservableField)和集合类型(Observable 容器类)。
通过继承BaseObservable来实现动态更新
public class Animal extends BaseObservable {
private String name;
private String area;
public Animal(String name, String area) {
this.name = name;
this.area = area;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
notifyPropertyChanged(BR.area);
}
}
只需要在getter 上使用@Bindable注解,在setter中通知更新就可以了。其中BR 是编译时生成的类,用@Bindable标记过的getter方法会在BR中生成一个相应的字段。在setter中调用notifyPropertyChanged(BR.name)通知系统BR.name这个字段的数据已经发生变化并更新UI。
// xml
<TextView
android:layout_width="wrap_content"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:text='@{animal.name+"-->"+animal.area}' />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="@{Onclicklistener}"
android:text="使用Observable更新数据" />
// Activity 中代码
Animal animal=new Animal("巨松鼠","西南部");
binding.setAnimal(animal);
binding.setOnclicklistener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animal.setName("华南虎");
animal.setArea("华南地区");
}
});
系统为我们提供的所有基本数据类型对应的Observable 类,比如ObservableInt、ObservableFloat、ObservableBoolean 等,也可以使用引用数据类型和基本数据类型通用的ObservableField,它们都继承自BaseObservable。
public class Human {
public ObservableField<String> name=new ObservableField<>();
public ObservableInt age=new ObservableInt();
public Human(String name, int age) {
this.name.set(name);
this.age.set(age);
}
public ObservableField<String> getName() {
return name;
}
public void setName(ObservableField<String> name) {
this.name = name;
}
public ObservableInt getAge() {
return age;
}
public void setAge(ObservableInt age) {
this.age = age;
}
}
// 代码
Human human=new Human("小溪",18);
binding.setHuman(human);
binding.setOnclicklistener(new View.OnClickListener() {
@Override
public void onClick(View v) {
human.name.set("小米");
human.age.set(20);
}
});
Observable容器类包括ObservableArrayList 和ObservableArrayMap。
// xml
<import type="androidx.databinding.ObservableList"/>
<import type="com.xf.databindingdemo.Human"/>
<variable
name="dataList"
type="ObservableList<Human>" />
...
<TextView
android:layout_width="wrap_content"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:text="@{dataList[0].name}" />
<TextView
android:layout_width="wrap_content"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:text="@{dataList[1].name}" />
// 代码
ObservableArrayList<Human> dataList=new ObservableArrayList<>();
Human human1=new Human("小溪",18);
dataList.add(human1);
Human human2=new Human("小米",18);
dataList.add(human2);
binding.setDataList(dataList);
binding.setOnclicklistener(new View.OnClickListener() {
@Override
public void onClick(View v) {
human1.name.set("花花");
human2.name.set("小夕");
}
});
从MVVM模式的角度来讲,双向绑定就是Model 和View 通过ViewModel 进行双向动态更新。
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@{user.name}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="@{Onclicklistener}"
android:text="双向绑定" />
定义EditText来改变Human 的name 字段,关键就是将“@{user.name}”改为“@={user.name}”。TextView来动态显示Human 的name 字段变化。
Human human2=new Human("小米",18);
binding.setUser(human2);
binding.setOnclicklistener(new View.OnClickListener() {
@Override
public void onClick(View v) {
human2.name.set("小夕");
human2.age.set(20);
}
});
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user"
type="com.xf.databindingdemo.Human" />
data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text='@{user.name,default="小米"}'
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{user.age+"",default="18"}'
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
layout>
class MyAdapter extends RecyclerView.Adapter<MyAdapter.VH>{
private List<Human> dataLIst;
public MyAdapter(List<Human> dataLIst) {
this.dataLIst = dataLIst;
}
@NonNull
@Override
public MyAdapter.VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLayoutBinding inflate = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_layout, parent, false);
return new VH(inflate);
}
@Override
public void onBindViewHolder(@NonNull MyAdapter.VH holder, int position) {
holder.getBinding().setUser(dataLIst.get(position));
}
@Override
public int getItemCount() {
if (dataLIst==null)
return 0;
return dataLIst.size();
}
class VH extends RecyclerView.ViewHolder{
private ItemLayoutBinding binding;
public VH(@NonNull ItemLayoutBinding binding) {
super(binding.getRoot());
this.binding=binding;
}
public ItemLayoutBinding getBinding(){
return binding;
}
}
}
viewDataBinding.recy.setLayoutManager(new LinearLayoutManager(this));
MyAdapter myAdapter=new MyAdapter(dataList);
viewDataBinding.recy.setAdapter(myAdapter);
效果如图
如果能帮助您,请点赞、关注