如果你要在 Android 实现 MVVM 架构, 那么 DataBinding 是你的不二抉择. MVVM 也是目前所有前端 /iOS/Android 畛域支流倒退方向
- 更少的代码
- 更弱小的容错性
- 更快的迭代速度
- 更高的可读性
本文与 2019 基于 Kotlin 再编辑
前言
- 不要希图应用 LiveData 取代 DataBinding, DataBinding 自身就兼容 LiveData 属性
- 无论我的项目大小 MVVM 都优于 M *3. 这是支流也是将来
启用 DataBinding 会主动在 build 目录下生成类. 因为被集成进 AndroidStudio 所以不须要你手动编译会实时编译, 并且反对大部分代码补全.
apply plugin: "kotlin-kapt" // Kotlin 应用 Databinding 必须增加
android{
/.../
dataBinding {enabled = true;}
}
结尾
- Databinding 不是代替 ButterKnife 之类的
findById
只是他的一个小小的辅助性能而已, 我举荐应用 Kotlin 来解决这个需要; - Databinding 的大部分状况下谬误提醒很欠缺, 个别 XML 书写谬误也易于排查
- 我想强调的是 Xml 中的
@{}
只做赋值或者简略的三元运算或者判空等不要做简单运算, 否则违反解耦准则. - 业务逻辑应该尽量在 Model 中
ViewModel
属于 DataBinding 主动生成的类
MVP 比照 MVVM 的劣势
- MVP 通过接口回调实现导致代码可读性差, 浏览程序不连贯
- MVP 无奈实现双向数据绑定
- MVP 的实现因人而异, 差异性导致浏览性差
- MVP 的代码量比 MVC 还要多, 属于通过晋升代码量来解耦, 代码量比 MVVM 几何倍增
- 前端任何平台都开始趋向于 MVVM, Web 畛域 MVVM 属于最成熟的利用
我开源一个基于 Kotlin 和 Databinding 个性的 RecyclerView 库: BRV, 具备无可比拟的简洁和 MVVM 个性;
布局
布局文件
<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
如果须要数据变动是视图也跟着变动则须要应用到以下两种办法
有两种形式:
继承 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 的区别
- BaseObservable 是实现了 Observable 的类, 帮咱们实现了监听器的线程平安问题.
- BaseObservable 应用了 PropertyChangeRegistry 来执行 OnPropertyChangedCallback
- 所以我不举荐你间接实现 Observable.
ObservableField
这属于第二种形式, 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>
序列化数据类型 - 下面说的这两种只会视图追随数据更新, 数据并不会追随视图刷新.
- ObservableField 同样反对 addOnPropertyChangedCallback 监听属性扭转
如果数据为 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 的类生成
@Bindable
用于数据更新主动刷新视图. 前面的数据绑定提到.
@BindingAdapter
创立一个 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){ }
@BindingMethods
如果你想创立一个 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
注解参数(必选)
- type: 字节码 即你的控件类
- attribute: XML 属性
- method: 函数名 即控件中的函数名称
留神
- 如果属性名和
@BindingAdapter
定义的 XML 属性雷同会抵触报错 - 如果控件类中曾经存在一个和你定义的属性相关联的函数 (例
setName
函数和android:name
属性就相关联)则会优先执行该函数
@BindingConversion
属性值主动进行类型转换
- 只能润饰
public static
办法. - 任意地位任意办法名都不限度
- DataBinding 主动匹配被该注解润饰的办法和匹配参数类型
- 返回值类型必须和属性 setter 办法匹配, 且参数只能有一个
- 要求属性值必须是
@{}
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
@InverseMethod
该注解属于 AndroidStudio3 之后提供的 inverse 系列
的新注解, 全部都是针对数据双向绑定.
在数据和视图的数据不对立时能够应用该注解 @InverseMethod
解决数据转换的问题
例如数据模型存储用户的 id 然而视图不显示 id 而是显示用户名(数据和视图的类型不统一), 咱们就须要在两者之间转换.
咱们须要两个函数: 设置数据到视图的函数 称为 set / 设置视图变更到数据的函数 称为 get
- 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>
@InverseBindingAdapter
参数:
- String
attribute
属性值(必填) - String
event
非必填, 默认值等于<attribute>AttrChanged
他和 @BindingAdapter
配合实现双向数据绑定
齐全的双向数据绑定须要三个函数
- set (数据到视图)
- get (视图到数据)
- notify (告诉 Databinding 视图曾经刷新能够更新数据 (Model) 了)
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();}
@InverseBindingMethods
同 @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 文件中应用的表达式 (用于赋值变量), @{}
外面除了能够执行办法以外还能够写表达式, 并且反对一些特有表达式
- 算术 + – / * %
- 字符串合并 +
- 逻辑 && ||
- 二元 & | ^
- 一元 + – ! ~
- 移位 >> >>> <<
- 比拟 == > < >= <=
- Instanceof
- Grouping ()
- 文字 – character, String, numeric, null
- Cast
- 办法调用
- Field 拜访
- Array 拜访 []
- 三元 ?:
防止空指针
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 中应用高阶函数须要匹配如下规定
- BindingAdapter 的函数参数要求是一个接口, 不反对 Kotlin 的函数类型参数
- 接口只容许一个函数
- 接口的办法签名 (返回值 | 参数) 和传递的高阶函数匹配
Lambda
高阶函数不容许自定义传递参数(否则须要批改接口). 所以能够应用 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(` 吴彦祖 `)}
ViewDataBinding
主动生成的 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()
.
OnRebindCallback
该监听器能够监听到布局绑定的生命周期
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);
}
});
OnPropertyChangedCallback
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;
}
}
});
DataBindingUtil
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”
DataBindingComponent
默认状况下 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
注解
留神
- 能够应用 include 不过不能作为 root 布局. merge 不能应用
- 如果没有主动生成 DataBinding 类能够先写个 variable(或者 make module 下)
- 即便你没有绑定数据(你可能会在网络申请胜利外面绑定数据), 然而只有视图创立实现就会自定绑定数据. 这个时候数据是空对象. 空对象的字段也会有默认值(String 的默认值是 NULL, TextView 就会显示 NULL); 并且如果你用了三元表达式, 空对象的三元表达式都为 false; 所以倡议不要思考空对象的状况;
- 如果你给一个要求值是布尔类型值的自定义属性 (
BindingAdapter
) 赋值一个函数, 空指针的状况会返回 false;
举荐插件
DataBindingSupport
通过快捷键 (alt + enter) 在 XML 布局中主动创立表达式和节点 , AS4 生效
DataBindingConvert
应用快捷键疾速将包裹布局为 layout, AS4 可用
相干教程
Android 根底系列教程:
Android 根底课程 U - 小结_哔哩哔哩_bilibili
Android 根底课程 UI- 布局_哔哩哔哩_bilibili
Android 根底课程 UI- 控件_哔哩哔哩_bilibili
Android 根底课程 UI- 动画_哔哩哔哩_bilibili
Android 根底课程 -activity 的应用_哔哩哔哩_bilibili
Android 根底课程 -Fragment 应用办法_哔哩哔哩_bilibili
Android 根底课程 - 热修复 / 热更新技术原理_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/6844903549223059463,如有侵权,请分割删除。