DataBinding常用注解

有哪些注解

  • @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高级篇

你可能感兴趣的:(DataBinding常用注解)