有哪些注解
- @Bindable
- @BindingAdapter
- @BindingConversion
- @BindingMethod
- @BindingMethods
- @InverseBindingAdapter
- @InverseBindingMethod
- @InverseBindingMethods
- @InverseMethod
- @Untaggable
- @BindingBuildInfo
以上就是DataBinding中所有的注解,一共11个注解,其中@BindingBuildInfo与@Untaggable这两个注解是hide的,最常用的只有如下2个注解:
- @Bindable
- @BindingAdapter
@Bindable
Observable接口提供给开发者添加/移除监听者的机制。为了使开发更便捷,我们创建了BaseObservable类,它已经实现了Observable接口中的注册监听者的机制。
继承自BaseObservable的数据类,仍需手动的通知监听者们数据已发生变更。你可以在setter方法中发出变更消息,记住同时在getter方法上标记注解@Bindable。
@Bindable 注解的推荐用法 是修饰继承自Observable类中的getter accessor方法,但其实getter accessor的属性也是可以应用该注解的。
使用@Bindable注解标记的get方法,在编译时,会在BR类中生成对应的字段,然后与notifyPropertyChanged()方法配合使用,当该字段中的数据被修改时,dataBinding会自动刷新对应view的数据。
/**
* 继承BaseObservable
* set方法notifyPropertyChanged
* get方法@Bindable
*/
class BindalbeTestModel: BaseObservable() {
var testStr: String? = null
set(value) {
field = value
notifyPropertyChanged(com.ghp.demo.databindingdemoproject.BR.testStr)
}
@Bindable
get() {
return field?:""
}
}
@BindingAdapter
- 用于标记修饰方法,方法必须为公共静态方法
- 方法的第一个参数的类型必须为View类型,不然报错
- 用来自定义view的任意属性
android自身实现了大量的Adapter,你可以在项目module的android.databinding.adapters包下找到这些代码。
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();
boolean requireAll() default true;
}
上面是源码中@BindingAdapter注解的定义,可以看到:
- value属性是一个String数组,用来存放自定义的属性,示例:android:onItemClick,app:onItemClick
- requireAll是一个布尔值,用来表示定义的所有属性是否必须都要使用。
示例一:
object DemoBindingAdapter {
/**
* BindingAdapter必须是static类型
* requireAll默认是true
* @BindingAdapter("imageUrl", "placeholder")
*/
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = true)
@JvmStatic
fun loadImageFromUrl(view: ImageView,
url: String,
drawable: Drawable) {
Glide.with(view.context)
.load(url)
.placeholder(drawable)
.into(view)
}
...
}
在上面的代码中,我们定义了2个属性,requireAll=true代表我们在使用时,必须要同时使用2个属性的, 不然就会报错;如果requireAll=false,可以只使用其中一个属性,也可以2个属性都使用。
示例二:
为RecyclerView设置adapter,比如:setOnItemClickListener,@BindingAdapter的使用可以简化代码的幅度,让写adapter变得更加简单。下面是例子,完整代码请参考DemoProject
abstract class BaseViewAdapter: RecyclerView.Adapter> {
val mContext: Context
val mLayoutInflater: LayoutInflater
var mListener: OnItemClickListener? = null
var mList: MutableList = mutableListOf()
abstract fun getLayoutResID(): Int
constructor(context: Context){
mContext = context
mLayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): AdapterBindingViewHolder<*> {
var binding: ViewDataBinding = DataBindingUtil.inflate(mLayoutInflater, getLayoutResID(), parent, false)
return AdapterBindingViewHolder(binding)
}
override fun getItemCount(): Int {
return mList.size
}
companion object {
/**
* 使用@BindingAdapter定义相关属性
**/
@BindingAdapter("android:onItemClick")
@JvmStatic
fun setUpAdapter(recyclerView: RecyclerView, onItemClickListener: OnItemClickListener) {
var adapter: BaseViewAdapter = (recyclerView.adapter ?: return) as? BaseViewAdapter ?: return
adapter.mListener = object : OnItemClickListener{
override fun onItemClick(adapter: BaseViewAdapter, model: Any, position: Int) {
onItemClickListener.onItemClick(adapter, model, position)
}
}
}
}
interface OnItemClickListener {
fun onItemClick(adapter: BaseViewAdapter, model: kotlin.Any, position: Int)
}
...
}
class RecyclerViewAdapter : BaseViewAdapter {
constructor(context: Context): super(context)
override fun getLayoutResID(): Int = R.layout.book_recycle_item
/**
* 由于同一个adapter未必只有一种ViewHolder,
* 可能有好几种View type,所以在onBindViewHolder中,
* 我们只能获取基类的ViewHolder类型,也就是BindingViewHolder,
* 所以无法去做具体的set操作,如setEmployee。
* 这时候就可以使用setVariable接口,然后通过BR来指定variable的name。
*/
override fun onBindViewHolder(holder: AdapterBindingViewHolder<*>?, position: Int) {
var bookModel: BookModel = mList[position] as BookModel
holder?.binding?.setVariable(com.ghp.demo.databindingdemoproject.BR.item, bookModel)
holder?.binding?.executePendingBindings()
holder?.itemView?.addClickAction {
mListener?.onItemClick(this, bookModel, position)
}
}
...
}
使用自定义的属性
通过上面的方式,我们就实现了通过在RecyclerView中配置属性达到为adapter设置点击监听
解释下BR里的变量:
BR中的常量是一种标识符,它对应一个会发生变化的数据,当数据改变后,你可以用该标识符通知DataBinding,很快,DataBinding就会用新的数据去更新UI。
那么,DataBinding如何知道哪些数据会变化呢?目前,我们可以确定,中的每一个variable是会变化的,所以DataBinding会为它们生成BR标识符。用@Bindable 注解的类中的getXXX方法(该类父类为BaseObservable或者实现Observable接口)对应一个会变化的数据,DataBinding也会为它们生成BR标识符。
@BindingConversion
- 作用于方法
- 被该注解标记的方法,被视为dataBinding的转换方法。
- 方法必须为公共静态(public static)方法,且有且只能有1个参数
有时候会遇到类型不匹配的问题,比如R.color.white是int,但是通过Data Binding赋值给android:background属性后,需要把int转换为ColorDrawable。
官网上的示例:
把整型的颜色值转换为drawable对象:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
@BindingMethod与@BindingMethods
BindingMethods包含若干BindingMethod,BindingMethod是BindingMethods的子集。BindingMethods内部有一个BindingMethod数组,存放的是一个一个的BindingMethod。
- @BindingMethods注解一般用于标记类
- @BindingMethod注解需要与@BindingMethods注解结合使用才能发挥其功效
- 用法极其简单,但是使用场景很少(因为大多数场景,dataBinding已经帮我们做好了)
有3个字段,这3个字段都是必填项,少一个都不行:
- type:要操作的属性属于哪个View类,类型为class对象,比如:ImageView.class
- attribute:xml属性,类型为String ,比如:”bindingMethodToast”
- method:指定xml属性对应的set方法,类型为String,比如:”showBindingMethodToast”
/**
* BindingMethods与BindingMethod定义了一个自己声明的属性:bindingMethodToast
*
* 该属性与TestEditText里的showBindingMethodToast绑定
*/
@BindingMethods(BindingMethod(type = EditText::class, attribute = "bindingMethodToast", method = "showBindingMethodToast"))
class TestEditText : EditText {
...
fun showBindingMethodToast(s: String) {
if(s.isNullOrEmpty()){
return
}
Toast.makeText(context, s, Toast.LENGTH_SHORT).show()
}
}
在XML中的代码:
效果就是每输入一个字,就会弹出toast
@InverseBindingAdapter
- 作用于方法,方法须为公共静态方法。
- 方法的第一个参数必须为View类型,如TextView等
- 用于双向绑定
- 需要与@BindingAdapter配合使用
现在假设一种情况,当你更换成EditText时,如果你的用户名User.name已经绑定到EditText中,当用户输入文字的时候,你原来的user.name数据并没有同步改动,因此我们需要修改成:
看出微小的差别了吗?对,就是"@{}"改成了"@={}"
双向绑定发现的问题:
- 死循环绑定:因为数据源改变会通知view刷新,而view改变又会通知数据源刷新,这样一直循环往复,就形成了死循环绑定。
- 数据源中的数据有时需要经过转换才能在view中展示,而view中展示的内容也需要经过转换才能绑定到对应的数据源上。
死循环绑定的解决方式:只处理新旧数据不一样的数据,参考源码中的例子:android.databinding.adapters.TextViewBindingAdapter
需要注意的是,使用该语法必须要要反向绑定的方法,android原生view都是自带的,所以使用原生控件无须担心,但是自定义view的话需要我们通过InverseBindingAdapter注解类实现,
下面是自定义view双向绑定的使用:
TestEditText:
/**
* 双向绑定
* 在xml属性上使用语法"@={}"
* 自定义view通过InverseBindingAdapter注解类实现
* event不是必须的,可以省略,event的命名方式是+ AttrChanged,例如:textAttrChanged
* 需要BindingAdapter告诉框架如何处理event事件
* BindingAdapter需要设置requireAll = false,否则系统将识别不了textAttrChanged属性
* InverseBindingListener调用onChange告知发生变化,所有双向绑定,最后都是通过这个接口来observable改变的,各种监听
*/
class TestEditText : EditText {
companion object {
var value: ObservableField = ObservableField("")
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
@JvmStatic
fun captureTextValue(view: TextView): String {
var newValue: CharSequence = view.text
var oldValue: CharSequence = value.get()
//避免死循环
if (oldValue == null) {
value.set(newValue)
} else if (newValue != oldValue) {
value.set(newValue)
}
return value.get().toString()
}
@BindingAdapter(value = arrayOf("android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"),
requireAll = false)
@JvmStatic
fun setTextWatcher(view: TextView,
before: TextViewBindingAdapter.BeforeTextChanged?,
on: TextViewBindingAdapter.OnTextChanged?,
after: TextViewBindingAdapter.AfterTextChanged?,
textAttrChanged: InverseBindingListener?) {
var newValue: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
after?.afterTextChanged(s)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
before?.beforeTextChanged(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
on?.onTextChanged(s, start, before, count)
textAttrChanged?.onChange()
}
}
var oldValue: TextWatcher? = ListenerUtil.trackListener(view, newValue, R.id.textWatcher)
oldValue?.apply {
view.removeTextChangedListener(oldValue)
}
view.addTextChangedListener(newValue)
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
...
}
参考:
- DataBinding·常用注解说明
- DataBinding使用教程(三):各个注解详解:
- 双向绑定
- DataBinding使用教程(四):BaseObservable与双向绑定
- DataBinding使用全面详解
- BindingMethods与BindingMethod
- 从零开始的Android新项目8 - Data Binding高级篇