开启DataBinding
在 RecyclerView 中 , 如果要使用DataBinding架构组件进行数据绑定 , 首先要 启用 DataBinding , 并 导入 RecyclerView 依赖 ,
在 Module 模块下的 build.gradle.kts 构建脚本 中 , 配置如下内容 :
build.gradle.kts
1 2 3 4 5 6 7 8 9 10
| android { enable = true }
dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.10.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") }
|
修改主题
新建项目都是Jetpack Compose的主题了,修改为AppCompat的主题
1 2 3 4
| <?xml version="1.0" encoding="utf-8"?> <resources> <style name="Theme.Xhcontrolbrowser" parent="Theme.AppCompat.Light.NoActionBar" /> </resources>
|
布局
res下新建文件夹layout
添加activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?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> </data>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
|
创建的XML可以将光标放置在第一个字符位置 , 按下 Alt + 回车 , 弹出如下下拉菜单 ,转换为DataBinding的XML。
添加实体类
不自动更新
1
| class Student(var name: String, var age: Int) {}
|
单向绑定
方式1
如果想单向刷新
类继承BaseObservable,在需要更新字段的set方法中添加notifyChange();即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import androidx.databinding.BaseObservable; import androidx.databinding.Bindable;
public class Student extends BaseObservable { private String name; private int age;
public Student() {}
public Student(String name, int age) { this.name = name; this.age = age; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; notifyChange(); }
public String getName() { return name; }
public void setName(String name) { this.name = name; notifyChange(); } }
|
方式2
该类的属性用ObservableField封装同样的也可以实现单向绑定
1 2 3 4
| public class Person { public ObservableField<Integer> age = new ObservableField<>(); public ObservableField<String> name = new ObservableField<>(); }
|
也可以用一下类型
1 2 3 4 5 6 7 8 9 10 11
| BaseObservable, ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableField, ObservableFloat, ObservableInt, ObservableLong, ObservableParcelable, ObservableShort
|
ObservableCollection
dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap
该对象的属性会自带set和get方法,调用set方法即可实现页面控件绑定的数据自动刷新
1 2 3 4 5 6 7 8
| public class Presenter{ public void onClick(Person person){ person.name.set( "new test"); person.age.set( 30); Log.i("Presenter","onClick" + person.name); content.set("new content"); } }
|
双向绑定
对于输入控件,使用@={}表达式即可实现页面和绑定的值双向自动刷新
1 2 3 4 5 6 7 8 9 10 11
| <EditText android:id="@+id/editTextTextPersonName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="85dp" android:layout_marginBottom="7dp" android:ems="10" android:inputType="textPersonName" android:text="@={content}" app:layout_constraintBottom_toBottomOf="@+id/textView3" app:layout_constraintEnd_toEndOf="parent" />
|
代码中
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MainActivity extends AppCompatActivity { ActivityMainBinding activityMainBinding; ObservableField<String> content; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main); content = new ObservableField<>("我的内容"); activityMainBinding.setContent(content); } }
|
列表项的XML
list_item_user.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <?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="student" type="com.xhkjedu.xh_control_browser.model.Student" /> </data>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="50dip">
<TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{student.name}" android:textSize="24sp" tools:text="Tom" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<TextView android:id="@+id/age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(student.age)}" android:textSize="24sp" tools:text="18" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.7" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </layout>
|
创建的list_item_user.xml会自动生成类ListItemUserBinding
RecyclerView.Adapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.RecyclerView import com.xhkjedu.xh_control_browser.R import com.xhkjedu.xh_control_browser.databinding.ListItemUserBinding import com.xhkjedu.xh_control_browser.model.Student
class StudentAdapter : RecyclerView.Adapter<StudentAdapter.MyViewHolder>() { val stuList = mutableListOf<Student>()
fun addData(list: List<Student>) { stuList.addAll(list) notifyDataSetChanged() }
fun updateDataByIndex(index: Int) { notifyItemChanged(index) }
fun getData(): List<Student> { return stuList }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val itemBinding: ListItemUserBinding = DataBindingUtil.inflate<ListItemUserBinding>( LayoutInflater.from(parent.context), R.layout.list_item_user, parent, false ) return MyViewHolder(itemBinding) }
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.itemBinding.student = stuList[position] }
override fun getItemCount(): Int { return stuList.size }
class MyViewHolder : RecyclerView.ViewHolder {
lateinit var itemBinding: ListItemUserBinding
constructor(itemView: View) : super(itemView) constructor(itemBinding: ListItemUserBinding) : super(itemBinding.root) { this.itemBinding = itemBinding } } }
|
Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.LinearLayoutManager import com.xhkjedu.xh_control_browser.adapter.StudentAdapter import com.xhkjedu.xh_control_browser.databinding.ActivityMainBinding import com.xhkjedu.xh_control_browser.model.Student import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity() { private val coroutineScope = CoroutineScope(Dispatchers.IO) private val adapter = StudentAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
val activityMainBinding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
activityMainBinding.recyclerView.layoutManager = LinearLayoutManager(this) activityMainBinding.recyclerView.adapter = adapter loadData() }
fun loadData() { coroutineScope.launch { var i = 0 while (i < 5) { withContext(Dispatchers.Main) { if (i == 0) { adapter.addData( listOf( Student("Jerry", 12), Student("Mickey", 16), Student("Donald", 14) ) ) } else { adapter.getData()[0].age += 1 adapter.updateDataByIndex(0) } }
delay(3000) i++ }
} } }
|
这里更改属性的时候UI并没有刷新
要想自动刷新
可以把上面的实体使用ObservableField包一下
1
| class Student(var name: ObservableField(String), var age: ObservableField(Int){}
|
这样赋值的时候也要调用对应的方法才行,比较麻烦。
建议在页面上再使用ObservableField包裹。
SwipeRefreshLayout结合
添加依赖
1 2 3
| dependencies { implementation(libs.androidx.swiperefreshlayout) }
|
libs.versions.toml
1 2 3 4 5
| [versions] swiperefreshlayout = "1.1.0"
[libraries] androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
刷新状态绑定类
RefreshUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import android.graphics.Color;
import androidx.databinding.BindingAdapter; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
public class RefreshUtils { @BindingAdapter("refreshing") public static void setRefreshing(SwipeRefreshLayout view, boolean refreshing) { view.setProgressBackgroundColorSchemeColor(Color.parseColor("#f3f3f3")); view.setColorSchemeResources(android.R.color.holo_blue_bright, android.R.color.holo_green_light, android.R.color.holo_orange_light, android.R.color.holo_red_light); if(view.isRefreshing() != refreshing){ view.setRefreshing(refreshing); } } }
|
创建ViewModel
BaseViewModel.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import androidx.databinding.ObservableBoolean import androidx.databinding.ObservableField import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
open class BaseViewModel : ViewModel() { @JvmField var zIsShowMsg = ObservableBoolean(false) @JvmField var zShowMsgContent = ObservableField("")
@JvmField val isLoading = ObservableBoolean(false)
fun mLaunch(block: suspend () -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { block() } catch (e: Exception) { withContext(Dispatchers.Main){ zIsShowMsg.set(true) zShowMsgContent.set("接口请求失败") } } } }
fun loadDataWithLoading(block: suspend () -> Unit){ viewModelScope.launch(Dispatchers.IO) { try { isLoading.set(true) block() } catch (e: Exception) { withContext(Dispatchers.Main){ zIsShowMsg.set(true) zShowMsgContent.set("接口请求失败") } } isLoading.set(false) } } }
|
MainViewModel.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import android.util.Log import android.view.View import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableBoolean import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import com.google.gson.Gson import com.xhkjedu.xh_control_browser.api.ApiManager import com.xhkjedu.xh_control_browser.model.VisitModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
class MainViewModel : BaseViewModel() { private val TAG = "MainViewModel"
val visitList: ObservableArrayList<VisitModel> = ObservableArrayList()
fun loadData() { loadDataWithLoading { val result = ApiManager.appService.browserListPad().body() withContext(Dispatchers.Main) { result?.let { if (result.code == 0) { visitList.clear() visitList.addAll(result.obj)
Log.i(TAG, "loadData: " + Gson().toJson(visitList)) zIsShowMsg.set(false) } else { zIsShowMsg.set(true) zShowMsgContent.set(result.msg) } } } } }
fun onRefreshListener(): OnRefreshListener { return OnRefreshListener { loadData() } }
fun onClick(view: View?) { Log.i(TAG, "onClick") isLoading.set(false) } }
|
XML
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?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> <import type="com.xhkjedu.xh_control_browser.vm.MainViewModel" /> <variable name="viewModel" type="MainViewModel" /> </data>
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/swipe_refresh_view" onRefreshListener="@{viewModel.onRefreshListener()}" android:layout_width="match_parent" android:layout_height="match_parent" app:refreshing="@{viewModel.isLoading}">
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </RelativeLayout> </layout>
|
Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.xhkjedu.xh_control_browser.adapter.VisitAdapter import com.xhkjedu.xh_control_browser.databinding.ActivityMainBinding import com.xhkjedu.xh_control_browser.vm.MainViewModel
class MainActivity : AppCompatActivity() { private var TAG = "MainActivity" private val adapter = VisitAdapter() private lateinit var viewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this).get(MainViewModel::class.java) val activityMainBinding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) activityMainBinding.viewModel = viewModel activityMainBinding.recyclerView.layoutManager = LinearLayoutManager(this) activityMainBinding.recyclerView.adapter = adapter activityMainBinding.lifecycleOwner = this adapter.setData(viewModel.visitList) viewModel.loadData() } }
|
XML上的值绑定
数据绑定
字符串
数字转字符串
1
| @{String.valueOf(student.age)}
|
布尔转字符串
字符串拼接
双向绑定
显示
1 2 3 4 5 6 7 8 9 10 11
| <data> <import type="android.view.View" /> <import type="com.xhkjedu.xh_control_browser.vm.MainViewModel" /> <variable name="viewModel" type="MainViewModel" /> </data>
<androidx.cardview.widget.CardView android:layout_width="400dp" android:layout_height="400dp" android:visibility="@{viewModel.zIsShowMsg ? View.VISIBLE : View.GONE}"> </androidx.cardview.widget.CardView>
|
方法绑定
不带参数
Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MainActivity extends AppCompatActivity { ActivityMainBinding activityMainBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main); activityMainBinding.setPresenter(new Presenter()); } public class Presenter{ public void onClick(View view){ Log.i("Presenter","onClick"); } } }
|
布局中
在布局文件中,data节点设置该点击事件对象,然后在控件的android:onClick="@{presenter.onClick}"属性中设置绑定即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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="presenter" type="com.example.databinding.MainActivity.Presenter" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="147dp" android:layout_marginBottom="75dp" android:text="Button" android:onClick="@{presenter.onClick}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
|
带参数
1 2 3 4 5
| public class Presenter{ public void onClick(Person person){ Log.i("Presenter","onClick" + person.name); } }
|
XML中
1 2 3 4 5 6 7 8 9 10
| <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="147dp" android:layout_marginBottom="75dp" android:text="Button" android:onClick="@{()->presenter.onClick(person)}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" />
|
高级用法
DataBinding支持在普通方法上添加@注解来添加自定义控件属性,该方法需满足以下条件:
修饰方法, 要求方法必须public static
方法参数第一个要求必须是View
方法名不作要求
示例:
1 2 3 4 5 6
| @BindingAdapter("imageurl") public static void bindImageUrl(ImageView view,String url){ Glide.with(view) .load(url) .into(view); }
|
使用方法如下:
1 2 3 4 5 6
| <ImageView android:id="@+id/imageView" android:layout_width="150dp" android:layout_height="150dp" app:imageurl="@{item.head}" />
|
ViewBinding与DataBinding区别
1)ViewBinding
ViewBinding会根据xml布局文件自动生成对应的XXXBinding类,然后通过XXXBinding.inflate(layoutInflater)生成一个对应的binding对象, 这个binding对象包含了这个xml布局文件中具有 ID 的所有视图对象,可以直接引用,省去了findViewById的操作。 binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root)
2)DataBinding
DataBinding是一个数据绑定库,它将xml布局中的界面组件绑定到代码中的数据对象, 可以通过对实体字段添@Bindable注解结合notifyPropertyChanged()实现双向绑定,也可以通过对自定义view添加带@BindingAdapter注解的方法来实现自定义属性。 将xml改成databinding 布局后,这样就可以直接绑定并注入xml了: binding = DataBindingUtil.setContentView(this, R.layout.activity_xxx)
通过导包了解,ViewBinding自动生成的XXXBinding也属于DataBinding,也就是DataBinding包含了ViewBinding。