若是你要在Android实现MVVM架构, 那么DataBinding是你的不二选择. MVVM也是目前全部前端/iOS/Android领域主流发展方向前端
本文与2019基于Kotlin再编辑java
前言android
启用 DataBinding会自动在build目录下生成类. 由于被集成进AndroidStudio因此不须要你手动编译会实时编译, 而且支持大部分代码补全.git
apply plugin: "kotlin-kapt" // Kotlin 使用 Databinding必须添加
android{
/.../
dataBinding {
enabled = true;
}
}
复制代码
开头github
findById
只是他的一个小小的辅助功能而已, 我推荐使用Kotlin来解决这个需求;@{}
只作赋值或者简单的三元运算或者判空等不要作复杂运算, 不然违背解耦原则.ViewModel
属于DataBinding自动生成的类MVP对比MVVM的劣势api
我开源一个基于Kotlin和Databinding特性的RecyclerView库: BRV, 具有无与伦比的简洁和MVVM特性;数组
我平时项目开发必备框架安全
布局文件markdown
<layout>
<data>
<variable name="user" type="com.liangjingkanji.databinding.pojo.UserBean"/>
</data>
<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="com.liangjingkanji.databinding.MainActivity">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.userName}" />
</RelativeLayout>
</layout>
复制代码
layout网络
布局根节点必须是<layout>
. 同时layout只能包含一个View标签. 不能直接包含<merge>
data
<data>
标签的内容即DataBinding的数据. data标签只能存在一个.
variable
经过<variable>
标签能够指定类, 而后在控件的属性值中就可使用
<data>
<variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
</data>
复制代码
经过DataBinding的setxx()
方法能够给Variable设置数据. name值不能包含_
下划线
import
第二种写法(导入), 默认导入了java/lang
包下的类(String/Integer). 能够直接使用被导入的类的静态方法.
<data>
<!--导入类-->
<import type="com.liangfeizc.databindingsamples.basic.User" />
<!--由于User已经导入, 因此能够简写类名-->
<variable name="user" type="User" />
</data>
复制代码
使用类
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.userName}" />
<!--user就是在Variable标签中的name, 能够随意自定义, 而后就会使用type中的类-->
复制代码
Tip: user
表明UserBean这个类, 可使用UserBean中的方法以及成员变量. 若是是getxx()
会自动识别为xx
. 注意不能使用字符串android
, 不然会报错没法绑定.
class
<data>
标签有个属性<class>
能够自定义DataBinding生成的类名以及路径
<!--自定义类名-->
<data class="CustomDataBinding"></data>
<!--自定义生成路径以及类型-->
<data class=".CustomDataBinding"></data> <!--自动在包名下生成包以及类-->
复制代码
Tip:注意没有代码自动补全. 自定义路径Module/build/generated/source/apt/debug/databinding/
目录下, 基本上不须要自定义路径
默认:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ActivityMainBinding这个类根据布局文件名生成(id+Binding)
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserBean userBean = new UserBean();
userBean.setUserName("drake");
// setUser这个方法根据Variable标签的name属性自动生成
viewDataBinding.setUser(userBean);
}
}
复制代码
alias
<variable>
标签若是须要导入(import)两个同名的类时可使用alias
属性(别名属性)
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
复制代码
include
在include其余布局的时候可能须要传递变量(variable)值过去
<variable name="userName" type="String"/>
....
<include layout="@layout/include_demo" bind:userName="@{userName}"/>
复制代码
include_demo
<data>
<variable name="userName" type="String"/>
</data>
...
android:text="@{userName}"
复制代码
两个布局经过include
的bind:<变量名>
值来传递. 并且二者必须有同一个变量
DataBinding不支持merge标签传递变量
自动布局属性
DataBinding对于自定义属性支持很是好, 只要View中包含setter方法就能够直接在布局中使用该属性(这是由于DataBinding的库中官方已经帮你写好了不少自定义属性)
public void setCustomName(@NonNull final String customName) {
mLastName.setText("吴彦祖");
}
复制代码
而后直接使用(可是IDE没有代码补全)
app:customName="@{@string/wuyanzu}"
复制代码
可是setter方法只支持单个参数. app:
这个命名空间能够随意
若是须要数据变化是视图也跟着变化则须要使用到如下两种方法
有两种方式:
继承BaseObservable
public class ObservableUser extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return firstName;
}
// 注解才会自动在build目录BR类中生成entry, 要求方法名必须以get开头
@Bindable
public String getLastName() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName); // 须要手动刷新
}
}
复制代码
简化用法只须要数据模型继承BaseObservable
便可, 而后每次变动数据后调用notify()
函数既能够刷新视图. 不须要注解.
observableUser.name
observableUser.notifyChange()
复制代码
若是你没法继承能够经过实现接口方式也能够. 查看BaseObservable实现的接口本身实现便可, 也能够复制代码示例.
还能够监听属性改变事件
ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
}
});
复制代码
属性第一次改变时会回调两次, 以后都只回调一次. 若是使用notifyChange()
不会获得id(即i等于0). 使用
notifyPropertyChanged(i)
就能够在回调里面获得id.
BaseObservable和Observable的区别
这属于第二种方式, databinding默认实现了一系列实现Observable接口的字段类型
BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField<T>,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable<T extends Parcelable>,
ObservableShort,
ViewDataBinding
复制代码
示例
public class PlainUser {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
复制代码
对于集合数据类型ObservableArrayMap/ObservableArrayLis/ObjservableMap
等集合数据类型
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
复制代码
使用
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView android:text='@{user["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<TextView android:text='@{String.valueOf(1 + (Integer)user["age"])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
复制代码
Tip:
ObservableParcelable<Object>
序列化数据类型若是数据为LiveData一样支持, 而且ViewDataBinding能够设置生命周期.
经过表达式使用@=
表达式就能够视图刷新的时候自动更新数据, 可是要求数据实现如下两种方式修改才会触发刷新
<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textNoSuggestions" android:text="@={model.name}"/>
复制代码
这种双向绑定存在一个很大的问题就是会死循环. 数据变化(回调监听器)触发视图变化, 而后视图又会触发数据变化(再次回调监听器), 而后一直循环, 设置相同的数据也视为数据变化.
因此咱们须要判断当前变化的数据是否等同于旧数据
public class CustomBindingAdapter {
@BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) {
CharSequence oldText = view.getText();
if (!haveContentsChanged(text, oldText)) {
return; // 数据没有变化不进行刷新视图
}
view.setText(text);
}
// 本工具类截取自官方源码
private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
if ((str1 == null) != (str2 == null)) {
return true;
} else if (str1 == null) {
return false;
}
final int length = str1.length();
if (length != str2.length()) {
return true;
}
for (int i = 0; i < length; i++) {
if (str1.charAt(i) != str2.charAt(i)) {
return true;
}
}
return false;
}
}
复制代码
Tip:
根据我上面说的, 监听器至少回调两次(数据->视图, 视图-> 数据)
如下这种是无效的, 由于String参数传递属于引用类型变量并非常量, 须要用equals()
// 本段截取官方源码, 我也不知道这sb为何这么写
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
/**/ 复制代码
正确
if (text == null || text.equals(oldText) || oldText.length() == 0) {
return;
}
复制代码
总结就是若是没有默认实行的控件属性使用双向数据绑定 就须要你本身实现BindingAdapter注解
DataBinding经过注解来控制ViewModel的类生成
用于数据更新自动刷新视图. 后面的数据绑定提到.
建立一个XML属性和函数, 而后在属性中进行设置数据操做会进入该函数.
图片加载框架能够方便使用此方法.
@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
public static void loadImage(ImageView view, String url, Drawable error) {
Glide.with(view.getContext()).load(url).into(view);
}
复制代码
public static
boolean
类型是可选参数. 能够要求是否全部参数都须要填写. 默认true.requireAll
为false, 你没有填写的属性值将为null. 因此须要作非空判断.使用:
<ImageView android:layout_width="match_parent" android:layout_height="200dp" app:error="@{@drawable/error}" wuyanzu:imageUrl="@{imageUrl}" app:onClickListener="@{activity.avatarClickListener}" />
复制代码
能够看到命名空间能够随意, 可是若是在BindingAdapter的数组内你定义了命名空间就必须彻底遵照
例如:
// 这里省略了一个注解参数.
@BindingAdapter({ "android:imageUrl", "error" })
public static void loadImage(ImageView view, String url, Drawable error) {
if(url == null) return;
Glide.with(view.getContext()).load(url).into(view);
}
复制代码
Tip: 若是你的数据初始化是在异步的. 会回调方法可是数据为null(成员默认值). 因此咱们必需要首先进行判空处理.
Kotlin实现有两种方法
单例类+@JvmStatic
注解
object ProgressAdapter {
@JvmStatic
@BindingAdapter("android:bindName")
fun setBindName(view: View, name:String){
}
}
复制代码
顶级函数
@BindingAdapter("android:bindName")
fun setBindName(view: View, name:String){
}
// 因为顶级函数太多影响代码补全建议使用顶级扩展函数, 以后也能够在代码中方便使用
@BindingAdapter("android:bindName")
fun View.setBindName( name:String){
}
复制代码
若是你想建立一个XML属性而且和View中的函数关联(即会自动使用属性值做为参数调用该函数). 就应该使用@BindingMethods
注解一个类(该类无限制甚至能够是一个接口).
若是说@BindingAdapter
是建立一个新的函数功能给控件使用, 那么BindingMethod就是引导DataBinding使用控件自身的函数.
该注解属于一个容器. 内部参数是一个@BindingMethod
数组, 只能用于修饰类;
任意类或接口, 不须要覆写任何函数
官方示例:
@BindingMethods({ @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"), @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"), @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"), })
public class ProgressBarBindingAdapter {
}
复制代码
@BindingMethod
注解参数(必选)
注意
@BindingAdapter
定义的XML属性相同会冲突报错setName
函数和android:name
属性就相关联)则会优先执行该函数属性值自动进行类型转换
public static
方法.@{}
DataBinding表达式官方示例:
public class Converters {
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
@BindingConversion
public static ColorStateList convertColorToColorStateList(int color) {
return ColorStateList.valueOf(color);
}
}
复制代码
我写的Kotlin示例
@BindingConversion
fun int2string(integer:Int):String{
Log.d("日志", "(CusView.kt:92) int2string ___ integer = [$integer]")
return integer.toString()
}
复制代码
XML
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="m" type="com.example.architecture.Model" />
</data>
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<com.example.architecture.CusView android:bindName="@={m.age}" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</FrameLayout>
</layout>
复制代码
我这代码实际上会报错, 由于涉及到双向数据绑定, @BindingConversion
只会在数据设置视图的时候生效. 可是若是是视图设置数据则会走其余函数(get), 若是该函数返回的类型和Model中的类型不匹配则会报异常, 除非你将那个函数改成类型匹配的.
或者去掉=
符号不使用双向数据绑定
android:text
不能使用int转为string, 由于他自己能正常接收int(做为resourceID). 而后会报
android.content.res.Resources$NotFoundException: String resource ID #0xa
复制代码
该注解属于AndroidStudio3以后提供的inverse系列
的新注解, 所有都是针对数据双向绑定.
在数据和视图的数据不统一时可使用该注解@InverseMethod
解决数据转换的问题
例如数据模型存储用户的id可是视图不显示id而是显示用户名(数据和视图的类型不一致), 咱们就须要在二者之间转换.
咱们须要两个函数: 设置数据到视图的函数 称为set / 设置视图变动到数据的函数 称为get
简单示例:
在用户id和用户名之间转换. 存储id可是显示的时候显示用户名
class Model {
var name = "设计师"
@InverseMethod("ui2data")
fun data2ui():String{
return "设计师金城武"
}
fun ui2data():String{
return "设计师吴彦祖"
}
}
复制代码
使用
<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>
<variable name="m" type="com.example.architecture.Model" />
</data>
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<com.example.architecture.CusView android:text="@{m.data2ui(m.name)}" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</FrameLayout>
</layout>
复制代码
参数:
attribute
属性值(必填)event
非必填, 默认值等于 <attribute>AttrChanged
他和@BindingAdapter
配合实现双向数据绑定
彻底的双向数据绑定须要三个函数
set函数
, 以前已经写过了
@BindingAdapter("android:bindName")
fun TextView.setBindName(name:String?){
if (name.isNullOrEmpty() && name != text) {
text = name
}
}
复制代码
get函数
@InverseBindingAdapter(attribute = "android:bindName", event = "cus_event")
fun TextView.getBindName():String{
// 这里你能够对视图上的数据进行处理最终设置给Model层
return text.toString()
}
复制代码
notify函数
视图变化后要通知Databinding开始设置Model层, 一样要用到@BindingAdapter
, 不一样的是参数要求只能为InverseBindingListener
.
@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){
// 这个函数是监听TextWatch 官方源码 固然不一样的需求不一样的监听器
doAfterTextChanged {
inverseBindingListener.onChange() // 这行代码执行即通知数据刷新
}
}
复制代码
InverseBindingListener 是个接口只有一个函数, 他是notify函数必要的参数.
public interface InverseBindingListener {
/** * Notifies the data binding system that the attribute value has changed. */
void onChange();
}
复制代码
同@BindingMethods
类似
可是@InverseBindingMethods
是视图变动数据(get函数), 而BindingMethods
是数据到视图(set函数)
参数
public @interface InverseBindingMethod {
/** * 控件的类字节码 */
Class type();
/** * 自定义的属性 */
String attribute();
/** * nitify函数的名称 即用于通知数据更新的函数 */
String event() default "";
/** * 控件自身的函数名称, 若是省略即自动生成为 {attribute}AttrChange */
String method() default "";
}
复制代码
若是说BindingMethods是关联setter方法和自定义属性, 那么InverseBindingMethods就是关联getter方法和自定义属性;
setter
是更新视图的时候使用, 而getter
方法是更新数据时候使用的
比@BindingMethods
要多一个函数即notify函数
用于通知更新
@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){
doAfterTextChanged {
inverseBindingListener.onChange()
}
}
复制代码
示例:
@InverseBindingMethods( InverseBindingMethod( type = CusView::class, attribute = "android:bindName", method = "getName", event = "cus_event" ) )
object Adapter {
}
复制代码
attribute
属性值属于不存在的属性, 则须要再建立一个BindingAdapter
自定义属性来处理.查看下生成类中的视图更新数据的实现源码
private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of data.name
// is data.setName((java.lang.String) callbackArg_0)
java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv);
// 拿到变化的属性
// localize variables for thread safety
// data != null
boolean dataJavaLangObjectNull = false;
// data.name
java.lang.String dataName = null;
// data
com.liangjingkanji.databinding.Bean data = mData; // 拿到数据
dataJavaLangObjectNull = (data) != (null);
if (dataJavaLangObjectNull) {
data.setName(((java.lang.String) (callbackArg_0))); // 存储到数据
}
}
};
复制代码
因此若是你没用重写Inverse的数据变动方法
将没法让视图通知数据刷新.
// 该方法会在绑定布局的时候回调
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String dataName = null;
com.liangjingkanji.databinding.Bean data = mData;
if ((dirtyFlags & 0x1aL) != 0) {
if (data != null) {
// read data.name
dataName = data.getName();
}
}
// batch finished
if ((dirtyFlags & 0x1aL) != 0) {
// api target 1
com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName);
}
if ((dirtyFlags & 0x10L) != 0) {
// api target 1
// 重点是这段代码, 将上面建立的监听器传入setTextWatcher方法
com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr);
}
}
复制代码
@BindingBuildInfo
和@Untaggable
这两个注解是DataBinding自动生成Java类时使用的.
Bindable
设置数据刷新视图. 自动生成BR的ID
BindingAdapter
设置自定义属性. 能够覆盖系统原有属性
BindingMethod/BindingMethods
关联自定义属性到控件原有的setter方法
BindingConversion
若是属性不能匹配类型参数将自动根据类型参数匹配到该注解修饰的方法来转换
InverseMethod
负责实现视图和数据之间的转换
InverseBindingAdapter
视图通知数据刷新的
InverseBindingMethod/InverseBindingMethods
视图通知数据刷新的(若是存在已有getter方法可用的状况下)
BindingMethods系优先级高于BindingAdapter系列
全部注解的功能都是基于XML属性值为Databinding表达式才生效(即@{}
)
建议参考官方实现源码:
这里指的是XML文件中使用的表达式(用于赋值变量), @{}
里面除了能够执行方法之外还能够写表达式, 而且支持一些特有表达式
避免空指针
variable的值即便设置null或者没有设置也不会出现空指针异常.
这是由于官方已经用DataBinding的@BindingAdapter注解重写了不少属性. 而且里面进行了判空处理.
<variable
name="userName"
type="String"/>
.....
android:text="@{userName}"
复制代码
不会出现空指针异常.
dataBinding.setUserName(null);
复制代码
而且还支持特有的非空多元表达式
android:text="@{user.displayName ?? user.lastName}"
复制代码
就等价于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
复制代码
仍是须要注意数组越界的
集合
集合不属于java.lang*
下, 须要导入全路径.
<variable name="list" type="java.util.List<String>"/>
<variable name="map" type="java.util.Map<String, String>"/>
复制代码
上面这种写法会报错
Error:与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。
复制代码
由于<
符号须要转义.
经常使用转义字符
空格  ;  ;
< 小于号 <; <;
> 大于号 >; >;
& 与号 &; &; " 引号 "; "; ‘ 撇号 &apos; '; × 乘号 ×; ×; ÷ 除号 ÷; ÷;
正确写法
<variable name="list" type="java.util.List<String>"/>
<variable name="map" type="java.util.Map<String, String>"/>
复制代码
集合和数组均可以用[]
来获得元素
android:text="@{map["firstName"]}"
复制代码
字符串
若是想要在@{}
中使用字符串, 可使用三种方式
第一种:
android:text='@{"吴彦祖"}'
复制代码
第二种:
android:text="@{`吴彦祖`}"
复制代码
第三种:
android:text="@{@string/user_name}"
复制代码
一样支持@color或@drawable
格式化字符串
首先在strings中定义<string>
<string name="string_format">名字: %s 性别: %s</string>
复制代码
而后就可使用DataBinding表达式
android:text="@{@string/string_format(`吴彦祖`, `男`)}"
复制代码
输出内容:
名字: 吴彦祖 性别: 男
复制代码
默认值
若是Variable尚未复制就会使用默认值显示.
android:text="@{user.integral, default=`30`}"
复制代码
上下文
DataBinding自己提供了一个名为context的Variable. 能够直接使用. 等同于View的getContext()
.
android:text="@{context.getApplicationInfo().toString()}"
复制代码
引用其余控件
<TextView android:id="@+id/datingName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:layout_toRightOf="@id/iv_dating" android:text="活动" />
/...
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:layout_toRightOf="@id/iv_order" android:text="@{datingName.text}" />
复制代码
引用包含_
的控件id是能够直接忽略该符号. 例如tv_name
直接写tvName
.
谢谢 lambda 指出错误
不论顺序均可以引用
使用Class
若是想用Class做为参数传递, 那么该Class不能直接经过静态导入来使用. 须要做为字段常量来使用
DataBinding还支持在XML中绑定函数参数类型, 而且仍是Lambda和高阶函数类型, 这点比Java还先进.
即直接将对象做为和属性等同的方式在XML使用. 这就必须先手动建立一个对象. 稍显麻烦.
建立自定义属性
object EventDataBindingComponent {
/** * 在绑定视图时能够用于Model来处理UI, 因为破坏视图和逻辑解耦的规则不是很建议使用 * 这会致使不方便业务逻辑进行单元测试 * * @see OnBindViewListener 该接口支持泛型定义具体视图 * * @receiver View * @param block OnBindViewListener<View> */
@JvmStatic
@BindingAdapter("view")
fun View.setView(listener: OnBindViewListener) {
listener.onBind(this)
}
}
复制代码
上面使用到的接口
interface OnBindViewListener {
fun onBind(v: View) } 复制代码
高阶函数
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="v" type="com.liangjingkanji.databinding.MainActivity"/>
</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="设计师吴彦祖" android:onClick="@{v::click}"/>
</LinearLayout>
</layout>
复制代码
在XML中使用高阶函数须要匹配以下规则
高阶函数不容许自定义传递参数(不然须要修改接口). 因此可使用Lambda来进行控制.
建立一个多参数的函数
fun onBinding(v:View, name:String){
Log.d("日志", "(MainActivity.kt:45) this = [$v] name = [$name]")
}
复制代码
XML使用
view="@{(view) -> v.onBinding(view, `吴彦祖`)}"
复制代码
若是不使用参数
view="@{() -> v.onBinding(`吴彦祖`)} 复制代码
自动生成的DataBinding类都继承自该类. 因此都拥有该类的方法
void addOnRebindCallback(OnRebindCallback listener) // 添加绑定监听器, 能够在Variable被设置的时候回调 void removeOnRebindCallback(OnRebindCallback listener) // 删除绑定监听器 View getRoot() // 返回被绑定的视图对象 abstract void invalidateAll() // 使全部的表达式无效而且马上从新设置表达式. 会从新触发OnRebindCallback回调(能够看作重置) abstract boolean setVariable(int variableId, Object value) // 能够根据字段id来设置变量 void unbind() // 解绑绑定, ui不会根据数据来变化, 可是监听器仍是会触发的 复制代码
这里有三个方法须要重点讲解:
abstract boolean hasPendingBindings() // 当ui须要根据当前数据变化时就会返回true(数据变化后有一瞬间) void executePendingBindings() // 强制ui马上刷新数据, 复制代码
当你改变了数据之后(在你设置了Observable观察器的状况下)会立刻刷新ui, 可是会在下一帧才会刷新UI, 存在必定的延迟时间. 在这段时间内hasPendingBindings()
会返回true. 若是想要同步(或者说马上)刷新UI能够立刻调用executePendingBindings()
.
该监听器能够监听到布局绑定的生命周期
mDataBinding.addOnRebindCallback(new OnRebindCallback() {
/** * 绑定以前 * @param binding * @return 若是返回true就会绑定布局, 返回false则取消绑定 */
@Override public boolean onPreBind(ViewDataBinding binding) {
return false;
}
/** * 若是取消绑定则回调该方法(取决于onPreBind的返回值) * @param binding */
@Override public void onCanceled(ViewDataBinding binding) {
super.onCanceled(binding);
}
/** * 绑定完成 * @param binding */
@Override public void onBound(ViewDataBinding binding) {
super.onBound(binding);
}
});
复制代码
DataBinding也有个数据变动监听器, 能够监听Variable的设置事件
mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
/** * 会在DataBinding设置数据的时候回调 * @param sender DataBinding生成的类 * @param propertyId Variable的id */
@Override public void onPropertyChanged(Observable sender, int propertyId) {
ActivityMainBinding databinding = (ActivityMainBinding) sender;
switch (propertyId) {
case BR.data:
Log.d("日志", "(MainActivity.java:54) ___ Result = " + databinding.getData().getName());
break;
case BR.dataSecond:
break;
}
}
});
复制代码
DataBinding不只能够绑定Activity还能够绑定视图内容(View)
// 视图
static <T extends ViewDataBinding> T bind(View root) static <T extends ViewDataBinding> T bind(View root, DataBindingComponent bindingComponent) // 布局 static <T extends ViewDataBinding> T inflate(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent, DataBindingComponent bindingComponent) // 组件 static <T extends ViewDataBinding> T inflate(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent) // activity static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent) 复制代码
还有两个不经常使用的方法, 检索视图是否被绑定, 若是没有绑定返回nul
static <T extends ViewDataBinding> T getBinding(View view) // 和getBinding不一样的是若是视图没有绑定会去检查父容器是否被绑定 static <T extends ViewDataBinding> T findBinding(View view) 复制代码
其余的方法
// 根据传的BR的id来返回字符串类型. 可能用于日志输出
static String convertBrIdToString(int id) 复制代码
例如BR.name这个字段对应的是4, 就可使用该方法将4转成"name"
默认状况下BindingAdapter
注解针对全部的XML属性均可以使用. 而经过制定不一样的DatabindingComponent能够切换这些自定义属性.
建立DatabindingComponent的步骤:
建立自定义类, 类中存在包含使用@BindingAdapter
的函数, 无需静态函数.
这个时候AndroidStudio会自动生成DatabindingComponnent接口
建立DatabindingComponent派生类, 这个时候会提示有方法要求覆写. 若是你省略第一步骤则不会有.
经过DataBindingUtils工具将你自定义的派生类设置到Databinding中, 这里包含全局默认和单例.
第一步
class PinkComponent {
@BindingAdapter("android:bindName")
fun TextView.setBindName(name:String?){
if (!name.isNullOrEmpty() && name != text) {
text = "数据体"
}
}
@BindingAdapter("android:bindNameAttrChanged")
fun TextView.notifyBindName(inverseBindingListener: InverseBindingListener){
doAfterTextChanged {
inverseBindingListener.onChange()
}
}
@InverseBindingAdapter(attribute = "android:bindName")
fun TextView.getBindName():String{
return text.toString()
}
}
复制代码
第二步
class CusComponent : DataBindingComponent {
override fun getPinkComponent(): PinkComponent {
return PinkComponent() // 此处不能返回null
}
}
复制代码
第三步
设置默认组件都是由DataBindingUtils设置, 可是方法也有所不一样
static void setDefaultComponent(DataBindingComponent bindingComponent) static DataBindingComponent getDefaultComponent() 复制代码
以上这种设置必须在绑定视图以前设置, 而且是默认全局的, 只须要设置一次.
static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent) 复制代码
若是你没有执行setDefaultComponent
则选择经过函数单独传入, 则每次都要传入不然报错.
DatabindingComponent只能使用@BindingAdapter
注解
BindingAdapter
)赋值一个函数, 空指针的状况会返回false;DataBindingSupport
经过快捷键(alt + enter)在XML布局中自动建立表达式和节点 , AS4失效
DataBindingConvert
使用快捷键快速将包裹布局为layout, AS4可用