有哪些注解
- @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)
...
}
參考: