前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发MVVM中DataBinding的使用

Android开发MVVM中DataBinding的使用

作者头像
码客说
发布2024-04-12 09:32:05
1110
发布2024-04-12 09:32:05
举报
文章被收录于专栏:码客码客

开启DataBinding

在 RecyclerView 中 , 如果要使用DataBinding架构组件进行数据绑定 , 首先要 启用 DataBinding , 并 导入 RecyclerView 依赖 ,

在 Module 模块下的 build.gradle.kts 构建脚本 中 , 配置如下内容 :

build.gradle.kts

代码语言:javascript
复制
android {
    enable = true
}

dependencies {
	// 导入 RecyclerView 依赖
    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的主题

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.Xhcontrolbrowser" parent="Theme.AppCompat.Light.NoActionBar" />
</resources>

布局

res下新建文件夹layout

添加activity_main.xml

代码语言:javascript
复制
<?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。

image-20240410111930289
image-20240410111930289

添加实体类

不自动更新

代码语言:javascript
复制
class Student(var name: String, var age: Int) {}

单向绑定

方式1

如果想单向刷新

类继承BaseObservable,在需要更新字段的set方法中添加notifyChange();即可

代码语言:javascript
复制
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封装同样的也可以实现单向绑定

代码语言:javascript
复制
public class Person  {
  public ObservableField<Integer> age = new ObservableField<>();
  public ObservableField<String> name = new ObservableField<>();
}

也可以用一下类型

代码语言:javascript
复制
BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable,
ObservableShort

ObservableCollection dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap

该对象的属性会自带set和get方法,调用set方法即可实现页面控件绑定的数据自动刷新

代码语言:javascript
复制
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");
   }
 }

双向绑定

对于输入控件,使用@={}表达式即可实现页面和绑定的值双向自动刷新

代码语言:javascript
复制
<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" />

代码中

代码语言:javascript
复制

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

代码语言:javascript
复制
<?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

代码语言:javascript
复制
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 {
        // 获取 DataBinding 布局
        val itemBinding: ListItemUserBinding = DataBindingUtil.inflate<ListItemUserBinding>(
            LayoutInflater.from(parent.context),
            R.layout.list_item_user,
            parent,
            false
        )
        // 将 DataBinding 布局设置给 ViewHolder
        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 {
        /**
         * RecyclerView 列表项布局文件是 item.xml
         * 生成的对应的 DataBinding 类是 ItemBinding 类
         * ItemBinding 类等同于布局文件
         */
        lateinit var itemBinding: ListItemUserBinding

        constructor(itemView: View) : super(itemView)
        constructor(itemBinding: ListItemUserBinding) : super(itemBinding.root) {
            this.itemBinding = itemBinding
        }
    }
}

Activity

代码语言:javascript
复制
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)

        // 设置布局文件
        // 布局文件是 activity_main.xml
        // 该类名称生成规则是 布局文件名称 + Binding
        val activityMainBinding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        // 设置 RecyclerView 的 布局管理器 / 数据适配器
        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包一下

代码语言:javascript
复制
class Student(var name: ObservableField(String), var age: ObservableField(Int){}

这样赋值的时候也要调用对应的方法才行,比较麻烦。

建议在页面上再使用ObservableField包裹。

SwipeRefreshLayout结合

添加依赖

代码语言:javascript
复制
dependencies {
    implementation(libs.androidx.swiperefreshlayout)
}

libs.versions.toml

代码语言:javascript
复制
[versions]
swiperefreshlayout = "1.1.0"

[libraries]
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }

刷新状态绑定类

RefreshUtils.java

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
<?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

代码语言:javascript
复制
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)
        // 设置布局文件
        // 布局文件是 activity_main.xml
        // 该类名称生成规则是 布局文件名称 + Binding
        val activityMainBinding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        activityMainBinding.viewModel = viewModel
        // 设置 RecyclerView 的 布局管理器 / 数据适配器
        activityMainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
        activityMainBinding.recyclerView.adapter = adapter
        activityMainBinding.lifecycleOwner = this
        adapter.setData(viewModel.visitList)
        viewModel.loadData()
    }
}

XML上的值绑定

数据绑定

字符串

代码语言:javascript
复制
@{student.name}

数字转字符串

代码语言:javascript
复制
@{String.valueOf(student.age)}

布尔转字符串

代码语言:javascript
复制
@{enabled ? "真" :"假"}

字符串拼接

代码语言:javascript
复制
@{"年龄:"+person.age}

双向绑定

代码语言:javascript
复制
@={content}

方法绑定

不带参数

Activity

代码语言:javascript
复制
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}"属性中设置绑定即可。

代码语言:javascript
复制
<?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>

带参数

代码语言:javascript
复制
public  class Presenter{
    public void onClick(Person person){
        Log.i("Presenter","onClick" + person.name);
    }
}

XML中

代码语言:javascript
复制
<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 方法名不作要求

示例:

代码语言:javascript
复制
@BindingAdapter("imageurl")
public static void bindImageUrl(ImageView view,String url){
    Glide.with(view)
        .load(url)
        .into(view);
}

使用方法如下:

代码语言:javascript
复制
<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。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开启DataBinding
  • 修改主题
  • 布局
  • 添加实体类
    • 不自动更新
      • 单向绑定
        • 方式1
        • 方式2
      • 双向绑定
      • 列表项的XML
      • RecyclerView.Adapter
      • Activity
      • SwipeRefreshLayout结合
        • 添加依赖
          • 刷新状态绑定类
            • 创建ViewModel
              • XML
                • Activity
                • XML上的值绑定
                  • 数据绑定
                    • 方法绑定
                      • 不带参数
                      • 带参数
                  • 高级用法
                  • ViewBinding与DataBinding区别
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档