define
- Data binding, which binds data objects to XML layouts and supports bidirectional binding, is one way the Android team implements the MVVM architecture;
advantages
- Save a lot of template code: findViewById, onClickListener,setText, etc.
- Decouple the view from the logic without the clutter of MVC or the large number of interfaces defined by MVP.
- View and data object bidirectional binding, development only need to pay attention to the data object, do not need to relationship view operations;
- Simple logic can be done in XML (try not to implement logic in XML);
Simple to use
- To enable DataBinding support, add the following code to the Module build.gradle and sync the project;
android {
...
dataBinding {
enabled = true
}
}
Copy the code
- Create a data class, ArticleItem.kt
data class ArticleItem(val title:String, val author:String,val content:String,)
Copy the code
- Create an Activity that automatically generates the layout. Move the cursor to the root View in the layout file, press Alt + Enter, and select “Convert to Data Binding Layout” from the pop-up menu.
<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" XMLNS: tools = "http://schemas.android.com/tools" > / / statement in the middle of the data labels to use to the full path variable, class < data > < variable name = "articleInfo" type="com.jinyang.jetpackdemo.bean.ArticleItem" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" tools:context=".activity.ArticleListActivity"> <TextView android:id="@+id/tv_article_title" android:layout_width="wrap_content" android:layout_height="wrap_content" @{articleInfo.title} {articleInfo.title}} Still can use the default setting the default android: text = "@ {articleInfo. The title, the default = use DataBinding explanation}" android: textColor = "# 000000" android:textSize="20sp" android:textStyle="bold" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_article_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{articleInfo.author,default=JinYang}" android:textColor="#666666" android:textSize="14sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_article_title" /> <TextView android:id="@+id/tv_article_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:lines="2" Android: text = "@ {articleInfo. The content, the default = data binding to objects and XML data binding support two-way layout binding is a method of the android team implement MVVM framework}" android:textColor="#333333" android:textSize="18sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_article_author" /> < / androidx constraintlayout. Widget. Constraintlayout > < / layout > / / for the convenience of ArticleItem reuse, Can also use the import way to introduce < data > < import type = "com. Jinyang. Jetpackdemo. Beans. ArticleItem" / > < variable name = "articleInfo" <data> <import alias="ArticleInfo" type="com.jinyang.jetpackdemo.bean.ArticleItem" /> <variable name="articleInfo" type="ArticleInfo" /> </data> // The binding class defaults to the name of the layout file. ActivityArticleListBinding / / you can customize ViewDataBinding instance name by the following < data class = "ArticleListBinding" >... </data>Copy the code
- Assign a value to articleInfo in the Activity
class ArticleListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) <! -- val binding: ArticleListBinding = --> val binding: ActivityArticleListBinding = DataBindingUtil.setContentView(this, R.layout.activity_article_list) binding. ArticleInfo = ArticleItem("Android Jetpack Series "," Today "," Jetpack is a suite of libraries; \n" + "mainly includes four aspects: Architecture, Foundation, Behavior and UI;" @override public View onCreateView(@nonnull LayoutInflater Inflater, inflater, inflater, inflater, inflater) ViewGroup container, Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater, getContentViewId(), container, false); return binding.getRoot(); }Copy the code
Unidirectional data binding
- By default, normal functions and strings are not observable, which means that when you need them in a data-binding layout, you can only get their values when they are created, but not the corresponding data in subsequent operations.
- There are three implementations of Observable: BaseObservable, ObservableField, and ObservableCollection
BaseObservable
- BaseObservable provides notifyChange() (refreshes all range values) and notifyPropertyChanged() (updates only the corresponding BR, which is generated by annotating @bindable);
Observable Class ArticleItem2(var title: String, author: String, content: String) : BaseObservable() { @get:Bindable var author: String = author set(value) { field = value notifyPropertyChanged(BR.author) } @get:Bindable var content: String = Content set(value) {field = Value notifyChange()}} 2 ArticleListActivity : AppCompatActivity() { lateinit var articleInfo: ArticleItem2 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityArticleListBinding = DataBindingUtil.setContentView(this, R.layout.activity_article_list) articleInfo = ArticleItem2("Android Jetpack Series ", "Jetpack ", "Jetpack is a suite of libraries; ) / / can set the attributes of the listener to observe changes articleInfo. AddOnPropertyChangedCallback (object: Observable.OnPropertyChangedCallback() { override fun onPropertyChanged(sender: Observable, propertyId: Int) { when { BR.author == propertyId -> { LjyLogUtil.d("BR.author") } BR.content == propertyId -> { LjyLogUtil.d("BR.content") } BR._all == propertyId -> { LjyLogUtil.d("BR._all") } else -> { LjyLogUtil.d("propertyId:$propertyId") } } } }) binding.articleInfo=articleInfo binding.onClickPresenter=OnClickPresenter() } inner class OnClickPresenter { fun changeTitle() { articleInfo.title="${articleInfo.title}1" } fun changeAuthor() { articleInfo.author="${articleInfo.author}1" } fun ChangeContent () {articleinfo. content="${articleInfo.content}1"}}} // add button in 3.xml and call click event <? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data > <variable name="articleInfo" type="com.jinyang.jetpackdemo.bean.ArticleItem2" /> <variable name="onClickPresenter" type="com.jinyang.jetpackdemo.activity.ArticleListActivity.OnClickPresenter" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" tools:context=".activity.ArticleListActivity"> <TextView android:id="@+id/tv_article_title" android:layout_width="wrap_content" android:layout_height="wrap_content" Android: text = "@ {articleInfo. The title, the default = use DataBinding explanation}" android: textColor = "# 000000" android: textSize = "20 sp" android:textStyle="bold" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_article_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{articleInfo.author,default=JinYang}" android:textColor="#666666" android:textSize="14sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_article_title" /> <TextView android:id="@+id/tv_article_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:lines="2" Android: text = "@ {articleInfo. The content, the default = data binding to objects and XML data binding support two-way layout binding is a method of the android team implement MVVM framework}" android:textColor="#333333" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_article_author" /> <Button android:id="@+id/btn_title" android:layout_width="wrap_content" android:text="title+1" android:textAllCaps="false" app:layout_constraintLeft_toLeftOf="parent" android:onClick="@{()->onClickPresenter.changeTitle()}" app:layout_constraintTop_toBottomOf="@+id/tv_article_content" android:layout_margin="10dp" android:layout_height="wrap_content"/> <Button android:id="@+id/btn_author" android:layout_width="wrap_content" android:text="author+1" android:textAllCaps="false" app:layout_constraintLeft_toRightOf="@id/btn_title" android:layout_margin="10dp" app:layout_constraintTop_toBottomOf="@+id/tv_article_content" android:onClick="@{()->onClickPresenter.changeAuthor()}" android:layout_height="wrap_content"/> <Button android:id="@+id/btn_content" app:layout_constraintLeft_toRightOf="@id/btn_author" android:onClick="@{()->onClickPresenter.changeContent()}" android:layout_width="wrap_content" android:layout_margin="10dp" android:text="content+1" android:textAllCaps="false" app:layout_constraintTop_toBottomOf="@+id/tv_article_content" android:layout_height="wrap_content"/> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code
ObservableField
- Inherits BaseObservable with high limitations, requiring notify operations, and using ObservableField for ease of use;
- Is the official encapsulation of BaseObservable field annotations and refreshes.
- The official native provides encapsulation of basic data types, For example, ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableD Ouble and ObservableParcelable;
- Other types can also be declared using the ObservableField generic
Class ArticleItem3(title: String, author: String, content: String) {val title: String ObservableField<String> = ObservableField<String>(title) val author: ObservableField<String> = ObservableField<String>(author) val content: ObservableField<String> = ObservableField<String>(content) } //2. Inner class OnClickPresenter {fun changeTitle() {inner class OnClickPresenter {fun changeTitle() { articleInfo.title.set("${articleInfo.title.get()}1") } fun changeAuthor() { articleInfo.author.set("${articleInfo.author.get()}1") } fun changeContent() { articleInfo.content.set("${articleInfo.content.get()}1") } }Copy the code
ObservableCollection
- DataBinding also provides wrapper classes to replace the native List and Map, ObservableList and ObservableMap, respectively
/ / 1. Modify the variable tag < data > < variable name = "articleInfo" type = "androidx. Databinding. ObservableMap< String,String>" /> <variable name="onClickPresenter" type="com.jinyang.jetpackdemo.activity.ArticleListActivity.OnClickPresenter" /> </data> //2. Class ArticleListActivity: AppCompatActivity() {lateInit var articleInfo: ObservableArrayMap<String, String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityArticleListBinding = DataBindingUtil.setContentView(this, R.layout.activity_article_list) articleInfo = ObservableArrayMap() articleInfo.apply { put("title", "Android Jetpack Series ") put("author", "Jinyang ") put(" Content ", "Jetpack is a suite of libraries;" ) } binding.articleInfo = articleInfo binding.onClickPresenter = OnClickPresenter() } inner class OnClickPresenter { fun changeTitle() { articleInfo["title"]+="1" } fun changeAuthor() { articleInfo["author"]+="1" } fun changeContent() { articleInfo["content"]+="1" } } }Copy the code
Bidirectional data binding
- Refresh the view when the data changes, and change the data when the view changes
- The method of binding variables has one more equal sign than one-way binding. The code looks like this:
<EditText
android:id="@+id/edit_content"
android:layout_width="match_parent"
android:text="@={articleInfo.content}"
android:layout_height="wrap_content"/>
Copy the code
LiveData replaces Observable Fields
- Observable Fields, but Google’s official recommendation is to use LiveData instead of Observable Field;
- See Google’s two steps to replacing Observable Field with LiveData
- The life cycle awareness of LiveData is not much of an advantage over Observable Fields, since the Data Binding checks whether the view is active.
The advantage for LiveData is that it not only supports doubling, but can be used in conjunction with many architectural components such as Room and WorkManager. In conclusion, we recommend that you use LiveData. The method is also very simple, requiring only two steps.
//1. Replace Observable Fields Class ArticleItem4(Title: String, author: String, Content: String) with LiveData: ViewModel() { var title: MutableLiveData<String> = MutableLiveData<String>().apply { value = title } var author: MutableLiveData<String> = MutableLiveData<String>().apply { value = author } var content: MutableLiveData<String> = MutableLiveData<String>().apply { value = content } } //2. Set the lifecycleOwner of LiveData (lifecycleOwner) class ArticleListActivity: AppCompatActivity() {lateinit var articleInfo: ArticleItem4 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityArticleListBinding = DataBindingUtil.setContentView(this, R.layout.activity_article_list) // The view's binding class contains a setLifecycleOwner method that must be used to observe LiveData from a data binding layout. Binding. LifecycleOwner = This articleInfo = ArticleItem4("Android Jetpack Series ", "Jetpack ", "Jetpack is a suite of libraries; ) binding.articleInfo = articleInfo binding.onClickPresenter = OnClickPresenter() } inner class OnClickPresenter { fun changeTitle() { articleInfo.title.value+="6" } fun changeAuthor() { articleInfo.author.value+="6" } fun changeContent() { articleInfo.content.value+="6" } } }Copy the code
event
- Event binding is also a variable binding, except that the variable is set to the callback interface, and we used the button click event in our example above
Inner class OnClickPresenter {fun changeTitle(articleInfo:ArticleItem4) {articleInfo.title. Value +="6"} inner class OnClickPresenter {fun changeTitle(articleInfo:ArticleItem4) {articleInfo.title. Value +="6"} Fun changeAuthor() {articleinfo.author.value +="6"}} //2. data = <data > <variable name="articleInfo" type="com.jinyang.jetpackdemo.bean.ArticleItem4" /> <variable name="onClickPresenter" Type = "com. Jinyang. Jetpackdemo. Activity. ArticleListActivity. OnClickPresenter" / > < / data > / / 3. Binding in view "Button android:id="@+id/btn_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="title+1" android:onClick="@{()->onClickPresenter.changeTitle(articleInfo)}" /> <Button android:id="@+id/btn_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="author+1" android:onClick="@{()->onClickPresenter.changeAuthor()}" />Copy the code
BindingAdapter and BindingConversion
BindingAdapter
- The dataBinding provides a BindingAdapter annotation to support custom properties or to modify existing properties;
- Annotation values can be existing XML attributes, such as Android: SRC, Android :text, etc., or they can be customized and used in XML.
- Example 1: Add the suffix “-button” to the text of each Button
//1. Define a method similar to the extension function class ArticleListActivity: AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityArticleListBinding = DataBindingUtil.setContentView(this, R.layout.activity_article_list) binding.lifecycleOwner = this var articleInfo: ArticleItem4 = ArticleItem4("Android Jetpack Series ", "Jetpack ", "Jetpack is a suite of libraries; ) binding.articleInfo = articleInfo } } @BindingAdapter("android:text") fun setText(view: Button, text: Android :text='@{"title+1"}' <Button android:id="@+id/btn_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{"title+1"}' android:onClick="@{()->onClickPresenter.changeTitle(articleInfo)}" />Copy the code
- Example 2: Custom attributes
- Here with the help of a Google official image library Coil, this library is completely written in Kotlin, and the use of a lot of Kotlin features, especially coroutines;
- Coil adds many extension functions to the ImageView, so we can load the image in one line of code;
- Detailed use can refer to: Still using Glide? Check out The Google image library Coil, which is different!
Implementation (" IO. Coiling-kt: coiling-1.1.1 ") //2. Class ImageBean(url: String) {var url: MutableLiveData<String> = MutableLiveData<String>().apply { value = url } } //3. @BindingAdapter(" URL ") fun loadImage(View: ImageView, URL: String) {view.load(url) ljyLogutil.d ("url:${url}")} // 4.xml Type = "com. Jinyang. Jetpackdemo. Beans. ImageBean" / > / / 5. Used in the ImageView < ImageView app:layout_constraintTop_toBottomOf="@+id/edit_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" android:src="@mipmap/ic_launcher" android:layout_width="wrap_content" Android :layout_height="wrap_content"/> // AppCompatActivity() { lateinit var articleInfo: ArticleItem4 lateinit var image: ImageBean override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityArticleListBinding = DataBindingUtil.setContentView(this, R.layout.activity_article_list) binding. LifecycleOwner = this articleInfo = ArticleItem4("Android Jetpack series ", "Today ", "Jetpack is a suite of libraries;" ) binding.articleInfo = articleInfo image = ImageBean("https://pic1.zhimg.com/v2-dc32dcddfd7e78e56cc4b6f689a24979_is.jpg") binding.image=image binding.onClickPresenter = OnClickPresenter() } inner class OnClickPresenter { fun changeTitle(articleInfo: ArticleItem4) { articleInfo.title.value += "6" image.url.value="https://pic3.zhimg.com/v2-e5656460688d19f7358ab3a6055fe34a_720w.jpg?source=95cc6b4a" } fun changeAuthor() { articleInfo.author.value += "6" image.url.value="https://pic2.zhimg.com/v2-f6981776beae87401991b426fbe34fdd_720w.jpg?source=95cc6b4a" } fun changeContent() { articleInfo.content.value += "6" image.url.value="https://pic2.zhimg.com/v2-f2eddc2fe0e509de5bbeeb351ddc2c61_1440w.jpg?source=172ae18b" } } }Copy the code
BindingConversion
- DataBinding also supports conversion of data, or type conversion
@BindingConversion fun convertStringToDrawable(str: String): Drawable {return when (STR) {" red "-> {colorred (color.parsecolor ("#FF4081"))}" blue "-> { ColorDrawable(Color.parseColor("#3F51B5")) } else -> { ColorDrawable(Color.parseColor("#344567")) } } } @BindingConversion fun convertStringToColor(str: String): Int {return when (STR) {" red "-> {color.parsecolor ("#FF4081")}" blue "-> {color.parsecolor ("#3F51B5")} else -> { Color.parseColor("#344567") } } } <Button android:id="@+id/btn_title" android:layout_width="wrap_content" Android :layout_height="wrap_content" Android :background='@{" blue "}' Android :textColor='@{" red ",default=@color/colorAccent}' android:layout_height="wrap_content" Android :background='@{" blue "}' Android :textColor='@{" red ",default=@color/colorAccent}' android:text="title+1" android:onClick="@{()->onClickPresenter.changeTitle(articleInfo)}" /> <Button android:id="@+id/btn_author" android:layout_width="wrap_content" android:layout_height="wrap_content" Android :text="author+1" android:background='@{" blue "}' android:text="author+1" android:onClick="@{()->onClickPresenter.changeAuthor()}" />Copy the code
Binding list data
- Use RecyclerView BaseRecyclerViewAdapterHelper + DataBinding
- Customize Adapter:
class ArticleAdapter(data: MutableList<ArticleItem4>?) : BaseQuickAdapter<ArticleItem4, ArticleItemViewHolder>(R.layout.layout_item_article, data) { override fun convert(holder: ArticleItemViewHolder, item: ArticleItem4) { holder.binding? .articleInfo = item holder.binding? .executePendingBindings() } class ArticleItemViewHolder(view: View) : BaseViewHolder(view) { val binding: LayoutItemArticleBinding? = databindingutil.bind (view)}} // The latest BaseQuickAdapter provides a custom ViewHolder implementation, BaseDataBindingHolder, which can be used as follows: class ArticleAdapter(data: MutableList<ArticleItem4>?) : BaseQuickAdapter<ArticleItem4, BaseDataBindingHolder<LayoutItemArticleBinding> >(R.layout.layout_item_article, data) { override fun convert(holder: BaseDataBindingHolder<LayoutItemArticleBinding>, item: ArticleItem4) { holder.dataBinding? .articleInfo = item holder.dataBinding? .executePendingBindings() } }Copy the code
- Layout_item_article. XML layout
<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="articleInfo" type="com.jinyang.jetpackdemo.bean.ArticleItem4" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_article_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{articleInfo.title,default=titleText}" android:textColor="#000000" android:textSize="20sp" android:textStyle="bold" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_article_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{articleInfo.author,default=authorText}" android:textColor="#666666" android:textSize="14sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_article_title" /> <TextView android:id="@+id/tv_article_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:lines="2" android:text="@{articleInfo.content,default=contentText}" android:textColor="#333333" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_article_author" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code
- The Activity code is as follows
class ArticleList2Activity : AppCompatActivity() { lateinit var mAdapter:ArticleAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) LjyLogUtil.d("onCreate") val binding: ActivityArticleList2Binding = DataBindingUtil.setContentView(this, R.layout.activity_article_list2) binding.lifecycleOwner = this binding.rvArticleList.layoutManager = LinearLayoutManager(this) val articleList:MutableList<ArticleItem4> = ArrayList() articleList.add(ArticleItem4("title1","jinYang","content111")) articleList.add(ArticleItem4("title2","jinYang","content222")) articleList.add(ArticleItem4("title3","jinYang","content333")) mAdapter= ArticleAdapter(articleList) binding.rvArticleList.adapter=mAdapter binding.onClickPresenter2 = OnClickPresenter2() } inner class OnClickPresenter2 { fun addArticle() { mAdapter.addData(ArticleItem4("title${mAdapter.data.size}","jinYang","content${mAdapter.data.size}")) LjyLogUtil.d("addArticle") } fun removeArticle() { mAdapter.removeAt(0) LjyLogUtil.d("removeArticle") } } }Copy the code
- The activity_article_list2.xml layout is as follows
<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="onClickPresenter2" type="com.jinyang.jetpackdemo.activity.ArticleList2Activity.OnClickPresenter2" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".activity.ArticleList2Activity"> <Button android:id="@+id/btn_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{()->onClickPresenter2.addArticle()}" android:text="addArticle" /> <Button android:id="@+id/btn_remove" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{()->onClickPresenter2.removeArticle()}" android:text="removeArticle" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_article_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> </layout>Copy the code