在傳統(tǒng)的Android應(yīng)用開發(fā)中策添,布局文件通常只負(fù)責(zé)應(yīng)用界面的布局工作瘦癌,如果需要實現(xiàn)頁面交互就需要調(diào)用setContentView()將Activity、fragment和XML布局文件關(guān)聯(lián)起來。然后通過控件的id找到控件前硫,接著在頁面中通過代碼對控件進(jìn)行邏輯處理临梗。在這種傳統(tǒng)的開發(fā)方式中涡扼,頁面承擔(dān)了大部分的工作量,大量的邏輯處理需要在Activity盟庞、Fragment中進(jìn)行處理吃沪,因此頁面顯得臃腫不堪,維護(hù)起來也很困難什猖,為了減輕頁面的工作量票彪,Google提出了DataBinding(視圖綁定)。
一不狮、DataBinding的優(yōu)點降铸?
(1)解耦。將部分原屬于Activity/Fragment去實現(xiàn)的功能交由Model實體類去實現(xiàn)荤傲,從而實現(xiàn)了部分功能代碼的分離垮耳,既保證了Activity/Fragment過于臃腫,也便于后期代碼的維護(hù)和擴(kuò)展。
(2)避免重復(fù)無效代碼终佛。不再需要findViewById操作(其實這條不是那么重要俊嗽,因為采用kotlin編碼的話本身就沒有findViewById)。
總結(jié):Databinding的作用就是在XML文件里面實現(xiàn)數(shù)據(jù)的部分綁定從而避免將數(shù)據(jù)的全部展示交由Controller層去實現(xiàn)從而達(dá)到代碼的易于擴(kuò)展和后期的維護(hù)目的铃彰。
二绍豁、DataBinding的基本使用
先來了解一下DataBinding常用的幾個類:
- DataBindingUtil:在Activity/Fragment中獲取相關(guān)的Binding對象。
- BaseObservable:Bean可以繼承該抽象類牙捉,實現(xiàn)可觀察的模式竹揍,在set屬性的時候調(diào)用notifyPropertyChanged方法,喚起刷新操作邪铲,也可以調(diào)用notifyChange方法全部刷新芬位。
- Observable:Bean可以實現(xiàn)該接口,實現(xiàn)可觀察的模式带到,在set屬性的時候調(diào)用notifyPropertyChanged方法昧碉,喚起刷新操作,也可以調(diào)用notifyChange方法全部刷新揽惹。
- ObservableFloat:這不是一個類被饿,而是一類類的代表,如ObservableShort搪搏、ObservableParcelable等等狭握,可觀察的屬性,通過get和set方法操作相關(guān)的值疯溺。
- BaseObservableField<>:和上述類似论颅,泛型可以傳入String等類型,比上述定義的基類型更加自由喝检。
android {
...
dataBinding {
enabled = true
}
}
2.修改布局文件
以<layout>
為頭嗅辣,以</layout>
為尾。
<layout>
<data>
<variable
name="login"
type="com.jack.androidjetpack.login.Login" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3.點擊Build->Rebuild Project生成對應(yīng)的Binding
4.使用DataBindingUtil類來進(jìn)行視圖的綁定
Activity的處理方式
class LoginActivity : AppCompatActivity() {
var binding: ActivityLoginBinding? = null
var login: Login? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
login = Login("jack", "123456")
binding?.setLogin(login)
}
}
Fragment的處理方式
class PhoneCodeFragment : Fragment() {
private var param1: String? = null
private var param2: String? = null
private var binding: FragmentPhoneCodeBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentPhoneCodeBinding.inflate(inflater, container, false)
return binding?.root
}
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
PhoneCodeFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
5.在layout布局中添加data標(biāo)簽
經(jīng)過前面的幾個步驟挠说,我們已經(jīng)將Databinding和我們的XML文件綁定起來了澡谭,現(xiàn)在你點擊Databinding會發(fā)現(xiàn)直接可以跳轉(zhuǎn)到對應(yīng)的XML文件里面去了,現(xiàn)在我們就來看看如何給我們的XML文件里面的View設(shè)置值损俭。
在XML文件的layout標(biāo)簽下蛙奖,創(chuàng)建data標(biāo)簽,在data標(biāo)簽中再創(chuàng)建variable標(biāo)簽杆兵,variable標(biāo)簽主要用到的就是name屬性和type屬性雁仲,類似于Java語言聲明變量時,需要為該變量指定類型和名稱琐脏。新建一個名為Login的數(shù)據(jù)類攒砖。
data class Login(var userName: String, var password: String):BaseObservable()
然后在布局的 data 標(biāo)簽里聲明要使用到的變量名缸兔、類的全路徑等信息,如下所示:
<layout>
<data>
<variable
name="login"
type="com.jack.androidjetpack.login.Login" />
</data>
...
</layout>
如果 Login有多處用到吹艇,也可以直接將之 import 進(jìn)來惰蜜,這樣就不用每次都指明整個包名路徑了,而 java.lang.* 包中的類會被自動導(dǎo)入受神,所以可以直接使用抛猖。
<layout>
<data>
<import type="com.jack.androidjetpack.login.Login" />
<variable
name="login"
type="Login" />
</data>
...
</layout>
在XML文件中聲明好variable屬性后,接下來就可以在XML使用它了鼻听。使用variable屬性時需要使用到布局表達(dá)式: @{ }
财著。可以在布局表達(dá)式@{ }
中獲取傳入variable對象的值撑碴,如下所示:
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:hint="@string/prompt_name"
android:inputType="textEmailAddress"
android:selectAllOnFocus="true"
android:text="@{login.userName}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
android:text="@{login.password}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username" />
</androidx.constraintlayout.widget.ConstraintLayout>
最后在我們的Controller
層將我們的data
與model
相關(guān)聯(lián)撑教。
var binding: ActivityLoginBinding? = null
var login: Login? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
login = Login("jack", "123456")
binding?.setLogin(login)
}
到這里,一個最基礎(chǔ)的DataBinding的例子就結(jié)束了醉拓,接下來我們繼續(xù)往下看驮履。
三、DataBinding的進(jìn)階
進(jìn)階1:給控件添加響應(yīng)事件
方式一:直接在Controller層通過原來的方式添加
binding?.login?.setOnClickListener {
}
方式二:創(chuàng)建一個工具類廉嚼,在類中定義響應(yīng)的點擊事件
第一步:創(chuàng)建點擊的工具類
/**
* @author: zhoufan
* @date: 2021/8/30 11:14
*/
class ButtonClickListener {
fun click(view: View) {
Log.e("click","響應(yīng)登錄的點擊事件")
}
}
第二步:在XML文件中添加工具類
<variable
name="btnHandler"
type="com.jack.androidjetpack.login.ButtonClickListener" />
第三步:在XML文件中添加響應(yīng)事件
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{btnHandler::click}"
android:text="@string/action_sign_in"
/>
第四步:在Controller里面進(jìn)行關(guān)聯(lián)
class LoginActivity : AppCompatActivity() {
var binding: ActivityLoginBinding? = null
var login: Login? = null
var clickListener:ButtonClickListener?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
login = Login("jack", "123456")
clickListener= ButtonClickListener()
binding?.setLogin(login)
// 這一步必須要,否則點擊沒反應(yīng)
binding?.btnHandler = clickListener
}
}
進(jìn)階2:BindingAdapter
使用DataBinding庫時倒戏,DataBinding會針對控件屬性生成對應(yīng)的XXXBindingAdapter類怠噪,如TextViewBindingAdapter類,其對TextView的每個可以使用DataBinding的屬性都生成了對應(yīng)的方法杜跷,而且每個方法都使用了@BindingAdapter注解傍念,注解中的參數(shù)就是對應(yīng)View的屬性。
自定義BindingAdapter
編寫一個處理圖片的自定義BindingAdapter類葛闷。然后定義一個靜態(tài)方法憋槐,主要用于添加 BindingAdapter 注解,注解值是 ImageView 控件自定義的屬性名淑趾,如下所示阳仔。
class ImageBindingAdapter {
companion object {
@BindingAdapter("url")
@JvmStatic
fun loadImage(view: ImageView?, url: String?) {
var realValue: String? = null
if (url.equals("null")) {
realValue = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201412%2F27%2F111335whdlgodddosl6swq.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632887161&t=f6a13d2953ea7be364a111344b72a654"
}
if (!TextUtils.isEmpty(realValue)) {
Glide.with(view!!)
.load(realValue)
.into(view)
}
}
}
}
在XML文件里面直接引用
<ImageView
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_below="@+id/login"
android:layout_marginTop="16dp"
app:url="@{`null`}" />
有時候,我們需要自定義多個屬性扣泊,那如何處理呢近范?和一個參數(shù)一樣,我們只需要使用BindingAdapter添加參數(shù)即可延蟹,如下所示:
class ImageBindingAdapter {
companion object {
@BindingAdapter(value = ["url", "placeholder", "error"])
@JvmStatic
fun loadImage(view: ImageView?, url: String?, placeholder: Drawable?, error: Drawable?) {
var realValue: String? = null
if (url.equals("null")) {
realValue =
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201412%2F27%2F111335whdlgodddosl6swq.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632887161&t=f6a13d2953ea7be364a111344b72a654"
}
if (!TextUtils.isEmpty(realValue)) {
val options = RequestOptions()
options.placeholder(placeholder)
options.error(error)
Glide.with(view!!)
.load(realValue)
.apply(options)
.into(view)
}
}
}
}
對應(yīng)的XML文件為:
<ImageView
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_below="@+id/login"
android:layout_marginTop="16dp"
app:placeholder="@{ContextCompat.getDrawable(context, R.mipmap.ic_launcher)}"
app:error="@{ContextCompat.getDrawable(context, R.mipmap.ic_launcher)}"
app:url="@{`null`}" />
別忘記導(dǎo)入對應(yīng)的依賴
<import type="androidx.core.content.ContextCompat" />
<import type="com.jack.androidjetpack.R"/>
進(jìn)階3:雙向綁定
DataBinding的本身是對View層狀態(tài)的一種觀察者模式的實現(xiàn)评矩,通過讓View與ViewModel層可觀察的對象進(jìn)行綁定,當(dāng)ViewModel層數(shù)據(jù)發(fā)生改變時阱飘,View層也會自動進(jìn)行UI的更新斥杜,這種場景稱之為單向綁定虱颗。
但是在實際的開發(fā)過程中,單向綁定并不能滿足所有的需求蔗喂。例如有下面的場景:如果布局中有一個EditText忘渔,當(dāng)用戶在輸入框中輸入內(nèi)容時,我們希望對應(yīng)的Model類能夠?qū)崟r更新弱恒,這就需要雙向綁定辨萍,DataBinding同樣支持這樣的能力。
實現(xiàn)雙向綁定需要用到ObservableField
類返弹,它能夠?qū)⑵胀ǖ臄?shù)據(jù)對象包裝成一個可觀察的數(shù)據(jù)對象锈玉,數(shù)據(jù)類型可以是基本數(shù)據(jù)類型、變量义起、集合拉背,也可以是自定義類型。
第一步:修改我們的實體類
class Login : BaseObservable() {
@get:Bindable
var userName: String? = null
set(userName) {
field = userName
notifyPropertyChanged(BR.userName)
}
@get:Bindable
var password: String? = null
set(password) {
field = password
notifyPropertyChanged(BR.userName)
}
}
第二步:修改我們的實體類
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:hint="@string/prompt_name"
android:inputType="textEmailAddress"
android:selectAllOnFocus="true"
android:text="@={login.userName}" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_below="@+id/username"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
android:text="@={login.password}" />
將我們原來的@{login.userName}
換成@={login.userName}
就可以了默终。
四椅棺、DataBinding的實戰(zhàn)
在我們的實際開發(fā)過程中,RecyclerView算是使用非常頻繁的齐蔽,接下來我們就看看如何使用DataBinding對RecyclerView進(jìn)行處理两疚。
第一步:定義我們的實體類
class UserModel: BaseObservable() {
@get:Bindable
var name: String? = null
set(name) {
field = name
notifyPropertyChanged(BR.name)
}
@get:Bindable
var address: String? = null
set(address) {
field = address
notifyPropertyChanged(BR.address)
}
@get:Bindable
var age: String? = null
set(age) {
field = age
notifyPropertyChanged(BR.age)
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.jack.androidjetpack.list.UserModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.address}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.age}" />
</LinearLayout>
</layout>?
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
var dataList: MutableList<UserModel> = mutableListOf()
set(value) {
field = value
notifyDataSetChanged()
}
inner class ViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
var adapterBinding: AdapterListBinding? = null
init {
adapterBinding = binding as AdapterListBinding
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.adapter_list,
parent,
false
)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val userModel = dataList[position]
holder.adapterBinding?.user = userModel
}
override fun getItemCount() = dataList.size
}
class ListActivity : AppCompatActivity() {
private var userModelList: MutableList<UserModel>? = mutableListOf()
private var activityListBinding: ActivityListBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityListBinding = DataBindingUtil.setContentView(this, R.layout.activity_list)
initData()
initRecyclerView()
}
private fun initData() {
for (i in 0..9) {
val userModel = UserModel()
userModel.name = "jack" + 1
userModel.address = "beijing$i"
userModel.age = "age$i"
userModelList?.add(userModel)
}
}
private fun initRecyclerView() {
val layoutManager = LinearLayoutManager(this)
activityListBinding?.recyclerView?.layoutManager = layoutManager
val adapter = UserAdapter()
activityListBinding?.recyclerView?.adapter = adapter
adapter.dataList = userModelList!!
}
}
好了,關(guān)于DataBinding的內(nèi)容就介紹到這里了含滴,當(dāng)然诱渤,實際開發(fā)過程中DataBinding使用的遠(yuǎn)遠(yuǎn)不止這些,需要我們平時多總結(jié)谈况,多思考才能有更多的收獲勺美。