DataBinding常用注解

有哪些注解

  • @Bindable
  • @BindingAdapter
  • @BindingConversion
  • @BindingMethod
  • @BindingMethods
  • @InverseBindingAdapter
  • @InverseBindingMethod
  • @InverseBindingMethods
  • @InverseMethod
  • @Untaggable
  • @BindingBuildInfo

以上就是DataBinding中所有的注解,一共11個注解,其中@BindingBuildInfo與@Untaggable這兩個注解是hide的,最常用的只有如下2個注解:

  • @Bindable
  • @BindingAdapter

@Bindable

Observable接口提供給開發(fā)者添加/移除監(jiān)聽者的機制。為了使開發(fā)更便捷,我們創(chuàng)建了BaseObservable類,它已經(jīng)實現(xiàn)了Observable接口中的注冊監(jiān)聽者的機制。

繼承自BaseObservable的數(shù)據(jù)類,仍需手動的通知監(jiān)聽者們數(shù)據(jù)已發(fā)生變更。你可以在setter方法中發(fā)出變更消息,記住同時在getter方法上標記注解@Bindable。

@Bindable 注解的推薦用法 是修飾繼承自O(shè)bservable類中的getter accessor方法,但其實getter accessor的屬性也是可以應(yīng)用該注解的。

使用@Bindable注解標記的get方法,在編譯時,會在BR類中生成對應(yīng)的字段,然后與notifyPropertyChanged()方法配合使用,當(dāng)該字段中的數(shù)據(jù)被修改時,dataBinding會自動刷新對應(yīng)view的數(shù)據(jù)。

/**
 * 繼承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

  • 用于標記修飾方法,方法必須為公共靜態(tài)方法
  • 方法的第一個參數(shù)的類型必須為View類型,不然報錯
  • 用來自定義view的任意屬性

android自身實現(xiàn)了大量的Adapter,你可以在項目module的android.databinding.adapters包下找到這些代碼。

@Target(ElementType.METHOD)
public @interface BindingAdapter {
    String[] value();
    boolean requireAll() default true;
}

上面是源碼中@BindingAdapter注解的定義,可以看到:

  • value屬性是一個String數(shù)組,用來存放自定義的屬性,示例: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設(shè)置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定義相關(guān)屬性
         **/
        @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}"
            />

通過上面的方式,我們就實現(xiàn)了通過在RecyclerView中配置屬性達到為adapter設(shè)置點擊監(jiān)聽

解釋下BR里的變量:
BR中的常量是一種標識符,它對應(yīng)一個會發(fā)生變化的數(shù)據(jù),當(dāng)數(shù)據(jù)改變后,你可以用該標識符通知DataBinding,很快,DataBinding就會用新的數(shù)據(jù)去更新UI。
那么,DataBinding如何知道哪些數(shù)據(jù)會變化呢?目前,我們可以確定,<data>中的每一個variable是會變化的,所以DataBinding會為它們生成BR標識符。用@Bindable 注解的類中的getXXX方法(該類父類為BaseObservable或者實現(xiàn)Observable接口)對應(yīng)一個會變化的數(shù)據(jù),DataBinding也會為它們生成BR標識符。

@BindingConversion

  • 作用于方法
  • 被該注解標記的方法,被視為dataBinding的轉(zhuǎn)換方法。
  • 方法必須為公共靜態(tài)(public static)方法,且有且只能有1個參數(shù)

有時候會遇到類型不匹配的問題,比如R.color.white是int,但是通過Data Binding賦值給android:background屬性后,需要把int轉(zhuǎn)換為ColorDrawable。
官網(wǎng)上的示例:

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

把整型的顏色值轉(zhuǎn)換為drawable對象:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

@BindingMethod與@BindingMethods

BindingMethods包含若干BindingMethod,BindingMethod是BindingMethods的子集。BindingMethods內(nèi)部有一個BindingMethod數(shù)組,存放的是一個一個的BindingMethod。

  • @BindingMethods注解一般用于標記類
  • @BindingMethod注解需要與@BindingMethods注解結(jié)合使用才能發(fā)揮其功效
  • 用法極其簡單,但是使用場景很少(因為大多數(shù)場景,dataBinding已經(jīng)幫我們做好了)

有3個字段,這3個字段都是必填項,少一個都不行:

  • type:要操作的屬性屬于哪個View類,類型為class對象,比如:ImageView.class
  • attribute:xml屬性,類型為String ,比如:”bindingMethodToast”
  • method:指定xml屬性對應(yīng)的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

  • 作用于方法,方法須為公共靜態(tài)方法。
  • 方法的第一個參數(shù)必須為View類型,如TextView等
  • 用于雙向綁定
  • 需要與@BindingAdapter配合使用

現(xiàn)在假設(shè)一種情況,當(dāng)你更換成EditText時,如果你的用戶名User.name已經(jīng)綁定到EditText中,當(dāng)用戶輸入文字的時候,你原來的user.name數(shù)據(jù)并沒有同步改動,因此我們需要修改成:

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

看出微小的差別了嗎?對,就是"@{}"改成了"@={}"

雙向綁定發(fā)現(xiàn)的問題:

  • 死循環(huán)綁定:因為數(shù)據(jù)源改變會通知view刷新,而view改變又會通知數(shù)據(jù)源刷新,這樣一直循環(huán)往復(fù),就形成了死循環(huán)綁定。
  • 數(shù)據(jù)源中的數(shù)據(jù)有時需要經(jīng)過轉(zhuǎn)換才能在view中展示,而view中展示的內(nèi)容也需要經(jīng)過轉(zhuǎn)換才能綁定到對應(yīng)的數(shù)據(jù)源上。

死循環(huán)綁定的解決方式:只處理新舊數(shù)據(jù)不一樣的數(shù)據(jù),參考源碼中的例子:android.databinding.adapters.TextViewBindingAdapter

需要注意的是,使用該語法必須要要反向綁定的方法,android原生view都是自帶的,所以使用原生控件無須擔(dān)心,但是自定義view的話需要我們通過InverseBindingAdapter注解類實現(xiàn),

下面是自定義view雙向綁定的使用:

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

TestEditText:

/**
 * 雙向綁定
 * 在xml屬性上使用語法"@={}"
 * 自定義view通過InverseBindingAdapter注解類實現(xiàn)
 * event不是必須的,可以省略,event的命名方式是+ AttrChanged,例如:textAttrChanged
 * 需要BindingAdapter告訴框架如何處理event事件
 * BindingAdapter需要設(shè)置requireAll = false,否則系統(tǒng)將識別不了textAttrChanged屬性
 * InverseBindingListener調(diào)用onChange告知發(fā)生變化,所有雙向綁定,最后都是通過這個接口來observable改變的,各種監(jiān)聽
 */
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()
            //避免死循環(huán)
            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)
  ...
}

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容