如果你要在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,如有侵权,请分割删除。