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个属性都使用。

<ImageView
            android:id="@+id/img"
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:visibility="@{showImage ? View.VISIBLE : View.GONE}"
            app:imageUrl="@{user.userPhotoUrl}"
            app:placeholder="@{@drawable/ic_launcher_background}"
/>
示例二:

为RecyclerView设置adapter,比如:setOnItemClickListener,@BindingAdapter的使用可以简化代码的幅度,让写adapter变得更加简单。下面是例子,完整代码请参考DemoProject

abstract class BaseViewAdapter: RecyclerView.Adapter<AdapterBindingViewHolder<*>> {
    val mContext: Context
    val mLayoutInflater: LayoutInflater
    var mListener: OnItemClickListener? = null

    var mList: MutableList<Any> = 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)
        }
    }
...
}

使用自定义的属性

<android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:onItemClick="@{presenter.onClick}"
            />

通过上面的方式,我们就实现了通过在RecyclerView中配置属性达到为adapter设置点击监听

解释下BR里的变量:
BR中的常量是一种标识符,它对应一个会发生变化的数据,当数据改变后,你可以用该标识符通知DataBinding,很快,DataBinding就会用新的数据去更新UI。
那么,DataBinding如何知道哪些数据会变化呢?目前,我们可以确定,<data>中的每一个variable是会变化的,所以DataBinding会为它们生成BR标识符。用@Bindable 注解的类中的getXXX方法(该类父类为BaseObservable或者实现Observable接口)对应一个会变化的数据,DataBinding也会为它们生成BR标识符。

@BindingConversion

  • 作用于方法
  • 被该注解标记的方法,被视为dataBinding的转换方法。
  • 方法必须为公共静态(public static)方法,且有且只能有1个参数

有时候会遇到类型不匹配的问题,比如R.color.white是int,但是通过Data Binding赋值给android:background属性后,需要把int转换为ColorDrawable。
官网上的示例:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

把整型的颜色值转换为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中的代码:

<com.ghp.demo.databindingdemoproject.view.TestEditText
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:text="@={viewModel.name}"
                app:bindingMethodToast="@{viewModel.name}"
                />

效果就是每输入一个字,就会弹出toast

@InverseBindingAdapter

  • 作用于方法,方法须为公共静态方法。
  • 方法的第一个参数必须为View类型,如TextView等
  • 用于双向绑定
  • 需要与@BindingAdapter配合使用

现在假设一种情况,当你更换成EditText时,如果你的用户名User.name已经绑定到EditText中,当用户输入文字的时候,你原来的user.name数据并没有同步改动,因此我们需要修改成:

<com.ghp.demo.databindingdemoproject.view.TestEditText
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:text="@={viewModel.name}"
                app:bindingMethodToast="@{viewModel.name}"
                />

看出微小的差别了吗?对,就是"@{}"改成了"@={}"

双向绑定发现的问题:

  • 死循环绑定:因为数据源改变会通知view刷新,而view改变又会通知数据源刷新,这样一直循环往复,就形成了死循环绑定。
  • 数据源中的数据有时需要经过转换才能在view中展示,而view中展示的内容也需要经过转换才能绑定到对应的数据源上。

死循环绑定的解决方式:只处理新旧数据不一样的数据,参考源码中的例子:android.databinding.adapters.TextViewBindingAdapter

需要注意的是,使用该语法必须要要反向绑定的方法,android原生view都是自带的,所以使用原生控件无须担心,但是自定义view的话需要我们通过InverseBindingAdapter注解类实现,

下面是自定义view双向绑定的使用:

<com.ghp.demo.databindingdemoproject.view.TestEditText
                android:layout_width="300dp"
                android:layout_height="50dp"
                android:text="@={viewModel.name}"
                />

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<CharSequence> = 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)
  ...
}

参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容