温文尔雅的花卷 · 【道中华】瓦氏夫人抗倭:若有战,召必往|总书 ...· 1 月前 · |
八块腹肌的草稿本 · 杨天石-全部作品在线阅读-微信读书· 2 月前 · |
千年单身的红金鱼 · 林震博士走进MIB《国际营销》课堂 - ...· 2 月前 · |
眉毛粗的木瓜 · 陵水三所新建学校今年9月开学 缓解主城区学位紧张· 6 月前 · |
傻傻的野马 · 金鹰卅爽•迎风而砺!第30届中国电视金鹰奖颁 ...· 1 年前 · |
如果你要在Android实现MVVM架构, 那么DataBinding是你的不二选择. MVVM也是目前所有前端/iOS/Android领域主流发展方向
本文与2019基于Kotlin再编辑
前言
启用 DataBinding会自动在build目录下生成类. 因为被集成进AndroidStudio所以不需要你手动编译会实时编译, 并且支持大部分代码补全.
apply plugin: "kotlin-kapt" // Kotlin 使用 Databinding必须添加
android{
/.../
dataBinding {
enabled = true;
}
开头
findById
只是他的一个小小的辅助功能而已, 我推荐使用Kotlin来解决这个需求;
@{}
只做赋值或者简单的三元运算或者判空等不要做复杂运算, 否则违背解耦原则.
ViewModel
属于DataBinding自动生成的类
MVP对比MVVM的劣势
我开源一个基于Kotlin和Databinding特性的RecyclerView库: BRV, 具备无与伦比的简洁和MVVM特性;
布局文件
<layout>
<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()
还可以监听属性改变事件
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:
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">
<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">
<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类时使用的.
@{}
)
这里指的是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">
<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接口
第一步
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可用
Android基础系列教程:
Android基础课程UI-布局_哔哩哔哩_bilibili
Android基础课程UI-控件_哔哩哔哩_bilibili
Android基础课程UI-动画_哔哩哔哩_bilibili
Android基础课程-activity的使用_哔哩哔哩_bilibili
Android基础课程-Fragment使用方法_哔哩哔哩_bilibili
Android基础课程-热修复/热更新技术原理_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/6844903549223059463 ,如有侵权,请联系删除。