首页 安卓& Kotlin Books 高级Android应用架构

11
MVVM样本具有数据绑定 由Aldo Olivares撰写

在最后一章中,您学习了如何通过使用LiveData和ViewModel等Google的体系结构组件重建Wewatch来实现MVVM体系结构。在本章中,您将学习如何通过使用的方式进一步提高您的应用程序的架构 数据绑定 库从活动中解耦XML布局。

一路走来学习:

  • 如何使用XML布局的数据绑定。
  • 如何使用与recyclerview适配器的数据绑定。
  • 如何实现单向数据绑定。
  • 如何实现双向数据绑定。
  • 如何创建可观察字段。
  • 如何使用数据绑定来观察ViewModels。

什么是数据绑定?

之前,实现数据在你的Android项目绑定,您首先需要了解数据绑定能为你做什么。根据官方文档 developer.android.com/topic/libraries/data -binding.:

数据绑定库是一个支持库,允许您使用声明性格式而不是编程方式将布局中的UI组件绑定到应用程序中的数据源。

简单地说,数据绑定可以显示你的XML布局如ConstraintLayouts或RecyclerViews内的变量或属性的值。

Typically (and without data binding), when you want to change or display a value inside your XML layout, you first need to get a reference to the View by using findViewById(). Once you have that, you can apply your changes:

findViewById<TextView>(R.id.text_view).apply {
    text = viewModel.userName
}

虽然这种方法不差,但它导致大量的样板代码可以在布局和活动或片段之间创建高水平的耦合。开发人员通常被迫创建许多设置的方法,在初步创建他们的观点后立即调用。使用数据绑定,您可以通过将变量直接分配给布局文件来保持UI更新:

<TextView
    android:text="@{viewmodel.userName}" />

In this example, the @{} syntax lets you display a property from viewmodel within the assignment expression. We’ll refer to this syntax as the 单向数据绑定 syntax. This approach helps you remove boilerplate code from your activities and fragments. And because you can assign default values, it also prevents memory leaks and NullPointerExceptions.

在接下来的几个部分中,您将学习如何在上一章中使用Wewatch应用程序中的数据绑定。

入门

首先打开本章的入门项目。您还可以使用上一章使用自己的项目。

实现数据绑定

默认情况下,未启用数据绑定。使用数据绑定库,打开 build.gradle. 并在Android块内添加以下行:

dataBinding {
  enabled = true
}

添加数据绑定到混播

在您的视图中实现数据绑定有四个步骤:

<data>
    <variable
      name="movie"
      type="com.raywenderlich.wewatch.data.model.Movie"/>
</data>
android:text="@{movie.title}"
android:text="@{movie.releaseDate}"
inner class MovieHolder(val binding: ItemMovieMainBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder {
  val layoutInflater = LayoutInflater.from(parent.context)
  val binding = DataBindingUtil.inflate<ItemMovieMainBinding>(layoutInflater, R.layout.item_movie_main, parent, false)
  return MovieHolder(binding)
}
//1
override fun onBindViewHolder(holder: MovieHolder, position: Int) {
  //2
  val movie = movies[position]
  //3
  holder.binding.movie = movie
  //4
  holder.binding.checkbox.setOnCheckedChangeListener{ checkbox, isChecked ->
    if (!selectedMovies.contains(movie) && isChecked) {
      selectedMovies.add(movies[position])
    }else{
      selectedMovies.remove(movies[position])
    }
  }
  //5
  holder.binding.checkbox.isChecked = selectedMovies.contains(movie)
}
@BindingAdapter("imageUrl")
fun ImageView.setImageUrl(url: String?) {
  Picasso.get().load(url).into(this)
}

@BindingAdapter("imageUrl")
fun ImageView.setImageUrl(int: Int) {
  this.setImageDrawable(resources.getDrawable(int,null))
}
if (movie.posterPath != null) {
  holder.binding.movieImageView.setImageUrl(
		RetrofitClient.TMDB_IMAGEURL + movie.posterPath)
} else {
  holder.binding.movieImageView.setImageUrl(
		R.drawable.ic_local_movies_gray)
}

将数据绑定添加到AddMovieActivity

To implement data binding in AddMovieActivity, you need to follow similar steps, with one key difference: You’ll use two-way data binding.

<CheckBox
    android:id="@+id/checkbox"
    android:checked="@{viewmodel.watched}"
    android:onCheckedChanged="@{viewmodel.watchedChanged}"
/>
<CheckBox
    android:id="@+id/checkbox"
    android:checked="@={viewmodel.watched}"
/>
<variable
  name="viewModel"
  type="com.raywenderlich.wewatch.viewmodel.AddViewModel"/>
var title= ObservableField<String>("")
var releaseDate = ObservableField<String>("")
//1
private val saveLiveData = MutableLiveData<Boolean>()
//2
fun getSaveLiveData(): LiveData<Boolean> = saveLiveData
//3
fun saveMovie() {
  if (canSaveMovie()) {
    repository.saveMovie(Movie(title= title.get(), releaseDate = releaseDate.get()))
    saveLiveData.postValue(true)
  } else {
    saveLiveData.postValue(false)
  }
}
//4
fun canSaveMovie(): Boolean {
  val title= this.title.get()
  title?.let {
    return title.isNotEmpty()
  }
  return false
}
android:text="@={viewModel.title}"
android:text="@={viewModel.releaseDate}"
android:onClick="@{()-> viewModel.saveMovie()}"
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  //1  
  val binding = DataBindingUtil.setContentView<ActivityAddBinding>(this, R.layout.activity_add)
  //2
  viewModel = ViewModelProviders.of(this).get(AddViewModel::class.java)
  //3
  binding.viewModel = viewModel
}
private fun configureLiveDataObservers() {
  viewModel.getSaveLiveData().observe(this, Observer { saved ->
    saved?.let {
      if (saved) {
        finish()
      } else {
        showMessage(getString(R.string.enter_title))
      }
    }
  })
}
configureLiveDataObservers()

挑战

You now know how to use data binding to improve your MVVM architecture. It’s time to put that knowledge into practice by refactoring 搜索MovieActivity.

关键点

  • 数据绑定允许您在XML布局内显示变量或属性的值。
  • 默认情况下未启用数据绑定;您需要在应用程序级别激活它 build.gradle..
  • 双向数据绑定允许您设置值并同时对更改作出反应。
  • The two-way binding syntax @={}, lets you update the appropriate values in the ObservableFields.
  • The one-way binding syntax @{}, lets you display a certain property from the viewmodel in the assignment expression.

然后去哪儿?

The Data Binding library works well with other Android建筑组件 such as the ViewModel. Also, data binding makes your code easy-to-read and maintain by providing a reliable way to bind your XML Layouts, thus reducing boilerplate.

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您对UI,UX,高亮反馈,或者我们的在线读者的其他功能,您可以发送天马设计团队与下面的表格:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解开这本书,我们的书籍和视频整个目录,具有raywenderlich.com专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。