本文主要是介绍databinding介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、DataBinding介绍
DataBinding 是 Google 在 Jetpack 中推出的一款数据绑定的支持库,利用该库可以实现在页面组件中直接绑定应用程序的数据源。使其维护起来更加方便,架构更明确简介。所谓的绑定,是绑定什么呢?
- 数据直接绑定到UI上,数据改变时UI自动更新
- UI上的数据绑定到变量中,当数据(如EditText中的数据)改变时自动更新
DataBinding非常适合用于MVVM模式中充当View和ViewModel的双向通讯的工具,引入DataBinding之后,我们可以少写很多的例如findViewById()之类的代码。
2、DataBinding使用介绍
2.1 引入DataBinding
要使用DataBinding只需要在build.grade中添加以下代码
android {...dataBinding{enabled = true}...}
2.2 布局文件格式转换
首先我们需要将传统的ViewGroup布局转换为固定的layout布局,然后在布局文件中声明所需要用到的数据实体类,布局文件格式如下。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity3"></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
传统布局转换为layout布局也很简单,选中根布局,点击鼠标右键->show Context Action ->convert to data binding layout即可自动生成。生成的布局分为两个部分,数据区和布局区,数据区内声明我们所需要用到的变量,布局区和传统的布局一样。
data
标签内进行变量声明和导入等
variable
标签进行变量声明
import
标签导入需要的类
<data><import type="com.example.model.Book" alias="mBook"/><import type="com.example.Utils.StringUtils"/><variablename="book"type="mBook"/><variablename="books"type="androidx.databinding.ObservableList<String>" /><variablename="bgColor"type="java.lang.String" /></data>
2.3 Activity和Fragment中使用databinding
Activity
public class MainActivity extends AppCompatActivity{private ActivityMainBinding mainBinding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);...}
}
Fragment
public class TopFragment extends Fragment {private FragmentTopBinding topFragmentBinding;@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {topFragmentBinding = DataBindingUtil.inflate(inflater,R.layout.topfragment,container,false);return topFragmentBinding.getRoot();}
}
注意:
上面的步骤2.2和2.3不能颠倒,因为只有在布局转换成layout样式后,databinding才会根据布局文件的名字自动生成一个对应的binding类,例如ActivityMainBinding和FragmentTopBinding,一般命名方式就是id+binding。如果我们xml文件命名不太规范,不好推测生成的类名,可以在build/generated/data_binding_base_class_source_out目录下查看生成的类。
生成的ActivityMainBinding类中持有布局文件中一些view的引用,一般来说有三类,根布局,含有**@{}绑定的view**,含有id属性的view。于是我们可以直接通过binding对象去获取视图中的一些view对象,而不需要在经过findViewById()的查找过程。
2.4 数据绑定
在data标签中,variable标签内声明变量名和类型,在布局中通过@{}进行引用,它支持的类型有
-
引用类型,比如一个类或者对象
-
基本数据类型,不需要导入对应的包,直接使用即可,由于databinding不会自动做类型转换,因此需要我们自己手动的转换,比如text标签内用String.valueOf()进行转换,rating标签内使用Float.valueOf()进行转换。既然我们可以调用String类中的方法,那么是不是也可以调用其他类中的一些方法呢?当然也是可以的,比如我们定义了一个静态的方法
public class StringUtils {public static String toUpperCase(String origin){return origin.toUpperCase();} }
然后同样在xml文件中通过import 进行导入
<import type="com.example.Utils.StringUtils"/>
接着就可以像调用String.valueOf()一样去调用它了。
-
数据集合类型
-
集合成员的获取方式有get和[ ]两种方式,比如我们传入一个集合books,可以通过"@{books.get(0).author}“或者”@{books[0].author}"来获取它的属性,如果值为null, 可以通过default 来指定其属性。
android:rating="@{book.rating,default=3.0}"
类似的用法还可以通过??或者是?:来实现, ??会取第一个不为null的值
android:text="@{book.rating != null ? book.rating : book.defaultRating}"
android:text="@{user.name ?? book.defaultRating}"
-
map类型的结构也可以通过get和[ ]两种方式获取,不过需要注意单引号和双引号的成对使用
"@{map.get(‘zhangsan’)}"或者是’@{map.get(“zhangsan”)}’
-
<TextViewandroid:id="@+id/text_publish_house"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{book.publishHouse}"app:layout_constraintBottom_toTopOf="@+id/guideline9"app:layout_constraintStart_toEndOf="@+id/textView7"app:layout_constraintTop_toTopOf="@+id/guideline8" /><TextViewandroid:id="@+id/text_pages"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{String.valueOf(book.pages)}"app:layout_constraintBottom_toTopOf="@+id/guideline10"app:layout_constraintStart_toEndOf="@+id/textView8"app:layout_constraintTop_toTopOf="@+id/guideline9" /><RatingBarandroid:id="@+id/ratingBar"android:layout_width="248dp"android:layout_height="62dp"android:numStars="5"android:stepSize="0.5"android:rating="@{book.rating,default=3.0}"app:layout_constraintBottom_toTopOf="@+id/guideline4"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toEndOf="@+id/textView4"app:layout_constraintTop_toTopOf="@+id/guideline2" />
别名
如果我们导入的包名字有冲突,可以在import标签内为其设置一个别名
<data><import type="com.example.model.Book" alias="mBook"/><import type="com.example.Utils.Book" alias="sBook"/><variablename="book"type="mBook"/></data>
在MainActivity中,导入视图创建ActivityMainBinding对象,通过ActivityMainBinding对象进行数据的注入,对于标签内声明的变量,ActivityMainBinding都会提供一个set方法。
public class MainActivity extends AppCompatActivity {private ActivityMainBinding mBinding;private ArrayList<Book> books;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);Book book = new Book("第一行代码(第三版)","郭霖","人民邮电出版社","2020-4",704,99,"9787115524836",3.0);books.add(book);mBinding.setBook(book);}
}
接下来在Activity中,我们就不需要在写findViewById()之类的代码,直接通过 mBinding就可以对布局中的对象进行引用。
隐式引用属性
在view上引用其他view的属性
<layout ...><data><import type="android.view.View"/></data><RelativeLayout ...><CheckBox android:id="@+id/seeAds" .../><ImageView android:visibility="@{seeAds.checked ? View.VISIBLE : View.GONE}" .../></RelativeLayout>
</layout>
include标签和ViewStub标签
如果我们在视图中通过include标签或者ViewStub标签导入了其他的布局,同样可以通过 dataBinding 来进行数据绑定,首先将需要包含的布局同样改为layout样式
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><import type="com.leavesc.databinding_demo.model.User" /><variablename="userInfo"type="User" /></data><android.support.constraint.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#acc"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:padding="20dp"android:text="@{userInfo.name}" /></android.support.constraint.ConstraintLayout>
</layout>
然后在包含进来的地方将数据传递给它
<includelayout="@layout/view_include"bind:userInfo="@{userInfo}" />
ViewStub的使用方式类似,这里就不叙述了。
2.5 数据对象
要实现数据变化时自动更新UI,有三种方法可以使用,分别是BaseObservable,ObservableField,ObservableCollection,下面一一介绍一下。
- BaseObservable
自定义一个数据对象,必须继承自BaseObservable,BaseObservable是Android原生的已经封装好的一个类,通过被观察者的方式对数据进行监听,通过注解@Bindable将成员绑定到视图,如果是private类型的成员,则将Bindable加在它的get方法上,视图中就可以引用了。
public class Book extends BaseObservable {// 图片id@Bindablepublic String imageId;// 书名@Bindablepublic String bookName;// 作者@Bindablepublic String author;// 出版社@Bindablepublic String publishHouse;// 出版年@Bindablepublic String publishYear;// 页数@Bindablepublic int pages;// 定价@Bindablepublic double price;// ISBN@Bindablepublic String isbn;// 评分@Bindablepublic Double rating;// 出厂价private double rarePrice;// 私有对象绑定到它的get方法上 @Bindableprivate double getRarePrice(){return this.rarePrice;}...}
那如何在数据更新时自动刷新UI呢?它提供了notifyChange()和notifyPropertyChanged(BR.xx)两个方法进行刷新,前者会刷新所有的成员,后者只会刷新对应的数据成员。示例如下
public void setBookName(String bookName) {this.bookName = bookName;notifyPropertyChanged(BR.bookName); // 只更新bookName这一个属性}
这时候如果在其他地方调用了book.setName()这个函数,UI就会被自动更新。上面使用到了BR这个类,它是什么呢,联想到R类,它应该也是系统自动生成的,我们在build/generated/ap_generated_sources/debug/out对应包的目录下可以找到这个类
public class BR {public static final int _all = 0;public static final int author = 1;public static final int name = 2;...
}
在这个类中自动对我们使用Bindable注解的成员进行编号。
监听器回调
当我们在UI中改变控件的某些数据时,如果想要接收到这个改变,那么可以给其添加上一个监听器
book.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {@Overridepublic void onPropertyChanged(Observable sender, int propertyId) {if (propertyId == BR.author){// TODO}}});
-
ObservableField
还记得LiveData吗?既然都是观察者模式,那么也可以将需要被观察的数据封装起来,从而不用继承BaseObservale也可以实现数据的绑定,官方提供了对基本类型的封装,如:ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可以通过ObservableField<>泛型来申明其它类型。
于是,我们上面的Book类也可以改造成
public class Book {// 图片idpublic ObservableInt imageId;// 书名public ObservableField<String> bookName;// 作者public ObservableField<String> author;// 出版社public ObservableField<String> publishHouse;// 出版年public ObservableField<String> publishYear;// 页数public ObservableInt pages;// 定价public ObservableDouble price;// ISBNpublic ObservableField<String> isbn;// 评分public ObservableFloat rating;...
}
注意:
Book类中必须为每个成员增加上get和set方法,一般我们在其中进行类型转换,比如上面的bookName我们将它转换成String类型再返回,总而言之,不需要向外界暴露出原本的类型。
observableCollection只是databinding提供的用于替换Java原生的List和Map,分别对应ObservableList和ObservableMap,其用法和原理和ObservableField没有太大的区别。
<variablename="books"type="androidx.databinding.ObservableList<String<" />
2.6 databinding支持的 表达式语法
上面我们就已经使用了字段访问,数组访问[],方法调用,三元运算符等,下面就总结一下xml文件中还支持哪些语法格式。
-
算术运算符 + - / * %
-
字符串连接运算符 +
-
逻辑运算符 && ||
-
二元运算符 & | ^
-
一元运算符 + - ! ~
-
移位运算符 >> >>> <<
-
比较运算符 == > < >= <=(请注意,< 需要转义为 <)
-
instanceof
-
分组运算符 ()
-
字面量运算符 - 字符、字符串、数字、null
-
类型转换
-
方法调用
-
字段访问
-
数组访问 []
-
三元运算符 ?:
3、数据的双向绑定
所谓双向绑定,就是当UI上的数据改变时也能更新到变量中,比如我们例子中的Book 星级评分rating, 可以让用户修改评星的同时也能同步到book对象中。使用方式很简单,只需要在原来的绑定方式上加一个=即可。
android:rating="@={book.rating,default=3.0}"
实例演示
。。。
4、 BindingAdapter
当某些属性需要自定义处理逻辑的时候可以使用BindingAdapter, 比如我们可以重新定义TextView的setText方法,让所有的英文全部转换为小写,又例如我们在Book类中保存了图片的id,如果直接在ImageView中直接将图片id传递给ImageView的src属性,图片是不会进行显示的,我们可以通过BindingAdapter重定义ImageView的setImageResource方法,图片就可以显示了。
图片显示例子
1、在Book类中重定义一个静态方法来处理ImageView的setImageResource方法 ,app是一个命名空间,可以不需要。
@BindingAdapter({"app:imageUrl"})public static void getTransImageView(ImageView imageView, int res){imageView.setImageResource(res);}
2、在布局文件中,ImageView标签内通过app:imageUrl设置图片
<ImageViewandroid:id="@+id/imageView"android:layout_width="185dp"android:layout_height="344dp"android:layout_marginStart="16dp"android:layout_marginLeft="16dp"app:imageUrl="@{book.imageId}"app:layout_constraintBottom_toTopOf="@+id/guideline2"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/textView" />
还可以使用BindingAdapter来自定义多个属性,下面看一个例子
public class ImageViewAdapter {/*** 定义多个属性* @param view* @param url* @param placeholder* @param error*/@BindingAdapter(value = {"imageUrl", "placeholder", "error"})public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {RequestOptions options = new RequestOptions();options.placeholder(placeholder);options.error(error);Glide.with(view).load(url).apply(options).into(view);}
}
在布局中使用上面定义的三个属性
<ImageViewandroid:layout_width="100dp"android:layout_height="100dp"android:layout_marginTop="10dp"app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"app:placeholder="@{@drawable/icon}"app:error="@{@drawable/error}"/>
此时,三个属性全部使用才能 BindingAdapter 才能正常工作,如果使用了其中的一些属性则不能正常编译通过,那么如何在自定义多个属性而正常使用其中的部分属性呢,@BindingAdapter 注解还有一个参数 requireAll ,requireAll 默认为 true,表示必须使用全部属性,将其设置为 false 就可以正常使用部分属性了,此时,自定义多个属性时要配置 注解 @BindAdapter 的 requireAll 属性为 false,参考如下:
// requireAll = false
@BindingAdapter(value = {"imageUrl", "placeholder", "error"},requireAll = false)
public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {RequestOptions options = new RequestOptions();options.placeholder(placeholder);options.error(error);Glide.with(view).load(url).apply(options).into(view);
}
在布局中,我们只使用到部分属性也不会编译报错了。
5、BindingConversion
有时候,在设置属性值的时候,会发生一些类型不匹配的情况,比如控件RatingBar的rating属性需要设置的float类型的值,但是我们的Book类中rating属性被设置为Double类型了,Book类在多个地方用到,如果要去改类型的话需要修改很多地方,这时就可以直接通过BindingConversion来进行方便的转换了。又比如 android:background 属性接收的是一个 Drawable 当我们在 databinding 的表达式中设置了一个颜色值,此时就需要 @BindingConvert
背景颜色转换例子
假设我们想要直接通过颜色名字设置布局背景,比如通过
mainBinding.setBgColor("skyBlue");
背景文件中是这样使用的
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data><import type="com.example.model.Book" alias="mBook"/><import type="com.example.Utils.StringUtils"/><variablename="book"type="mBook"/><variablename="books"type="androidx.databinding.ObservableList<String>" /><variablename="bgColor"type="java.lang.String" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/text_title"android:background="@{bgColor}"android:layout_width="match_parent"android:layout_height="match_parent"/></layout>
这时候一定会报错,因为background不接受一个String类型的参数,那么我们可以提供一个
函数对其进行转换
@BindingConversionpublic static ColorDrawable convertStringToDrawable(String color){if (color.equals("skyBlue")){return new ColorDrawable(Color.parseColor("#57faff"));}return new ColorDrawable(Color.parseColor("#000000"));
}
databinding会自动查找使用@BindingConversion注解的函数进行转换,能使用@BindingConversion注解的函数有几个要求
- 必须是静态的
- 只能由一个参数
- 被该注解标记的方法,被视为dataBinding的转换方法。
这篇关于databinding介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!