Ant*_*kov 7 data-binding android mvvm kotlin
我最近开始学习 Kotlin 中的视图模型和数据绑定。我创建了一个示例项目,在其中我在一个活动中创建了多个片段。我很想知道如何使用视图模型实现具有数据绑定的通用片段。我不确定这是否可能,或者我是否走在正确的道路上。我在网上搜索并找到了一些线索,但没有完整的解决方案。
到目前为止我所做的我创建了一个抽象的 BaseFragment。
abstract class BaseFragment<V : BaseViewModel> : Fragment()
{
lateinit var binding: FragmentHomeBinding
lateinit var viewModel : V
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
val view = binding.root
//here data must be an instance of the class MarsDataProvider
viewModel = ViewModelProviders.of(this).get(viewModel.javaClass)
setupUI()
binding.viewModel = viewModel
return view
}
abstract fun setupUI()
abstract fun getContentView() : Int
}
Run Code Online (Sandbox Code Playgroud)
这是 HomeFragment 的代码
class HomeFragment : BaseFragment<HomeViewModel>() {
override fun setupUI() {
viewModel.errorMessage.observe(this, Observer {
errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
})
}
override fun getContentView(): Int {
return R.layout.fragment_home
}
private var errorSnackbar: Snackbar? = null
private fun showError(@StringRes errorMessage:Int){
Log.d("anton","showError")
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
Log.d("anton","hideError")
errorSnackbar?.dismiss()
}
}
Run Code Online (Sandbox Code Playgroud)
这是我拥有的片段的 xml 布局之一
<?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="viewModel"
type="app.series.com.series4go.viewmodels.HomeViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:mutableVisibility="@{viewModel.getLoadingVisibility()}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:adapter="@{viewModel.getPostListAdapter()}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Run Code Online (Sandbox Code Playgroud)
我不知道如何改变
lateinit var binding: FragmentHomeBinding
Run Code Online (Sandbox Code Playgroud)
在 BaseFragment 中,它将是通用的,因为我需要用
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
Run Code Online (Sandbox Code Playgroud)
编辑
玩弄这段代码后,我来到了这个:
基础片段:
abstract class BaseFragment<V : BaseViewModel, T : ViewDataBinding> : Fragment()
{
lateinit var binding: T
lateinit var viewModel : V
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
val view = binding.root
viewModel = ViewModelProviders.of(this).get(getViewModelClass())
setupUI()
bindViewToModel()
return view
}
abstract fun setupUI()
abstract fun getContentView() : Int
abstract fun getViewModelClass() : Class<V>
abstract fun bindViewToModel()
}
Run Code Online (Sandbox Code Playgroud)
首页片段
类 HomeFragment : BaseFragment() {
override fun bindViewToModel() {
binding.viewModel = viewModel
}
override fun setupUI(){
binding.postList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
//here data must be an instance of the class MarsDataProvider
viewModel.errorMessage.observe(this, Observer { errorMessage ->
if (errorMessage != null) showError(errorMessage) else hideError()
})
}
override fun getContentView(): Int {
return R.layout.fragment_home
}
private var errorSnackbar: Snackbar? = null
private fun showError(@StringRes errorMessage:Int){
Log.d("anton","showError")
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
Log.d("anton","hideError")
errorSnackbar?.dismiss()
}
override fun getViewModelClass(): Class<HomeViewModel> {
return HomeViewModel::class.java
}
Run Code Online (Sandbox Code Playgroud)
}
在这个解决方案中,我唯一不喜欢的是函数 bindViewToModel,扩展基本片段的每个片段都需要在所有片段中以相同的方式实现它。不确定如何将它移动到基础片段,因为基础片段不知道布局的任何变量(因为它是抽象的)。
我很高兴知道是否有地方可以改进此设计或解决此问题。
谢谢
编辑 2
遵循@Oya Canl 的解决方案?我设法删除了抽象的 bindViewToModel 这是最后的代码,以防有人有兴趣使用它。
基础片段:
abstract class BaseFragment<V : BaseViewModel, T : ViewDataBinding> : Fragment()
{
lateinit var binding: T
lateinit var viewModel : V
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
val view = binding.root
viewModel = ViewModelProviders.of(this).get(getViewModelClass())
setupUI()
binding.setVariable(BR.viewModel, viewModel)
return view
}
abstract fun setupUI()
abstract fun getContentView() : Int
abstract fun getViewModelClass() : Class<V>
}
Run Code Online (Sandbox Code Playgroud)
首页片段
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>() {
override fun setupUI(){
binding.postList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
//here data must be an instance of the class MarsDataProvider
viewModel.errorMessage.observe(this, Observer { errorMessage ->
if (errorMessage != null) showError(errorMessage) else hideError()
})
}
override fun getContentView(): Int {
return R.layout.fragment_home
}
private var errorSnackbar: Snackbar? = null
private fun showError(@StringRes errorMessage:Int){
Log.d("anton","showError")
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
Log.d("anton","hideError")
errorSnackbar?.dismiss()
}
override fun getViewModelClass(): Class<HomeViewModel> {
return HomeViewModel::class.java
}
}
Run Code Online (Sandbox Code Playgroud)
Oya*_*nlı 10
数据绑定类的通用类型是ViewDataBinding。所以你可以得到你的绑定实例:
val binding = DataBindingUtil.inflate<ViewDataBinding>(
inflater, getContentView(), container, false)
Run Code Online (Sandbox Code Playgroud)
但是你不能像binding.viewModel那样设置viewModel,因为通用绑定类的实例不会有一个名为setViewModel的setter。你可以使用通用的 setVariable 来代替:
binding.setVariable(BR.viewModel, viewModel)
Run Code Online (Sandbox Code Playgroud)
BR 是一个生成的类,其中包含您用于数据绑定的所有变量。上面的这个方法不是类型安全的,它不会检查提到的 BR 变量是否确实位于特定的绑定类中。这就是为什么通常最好使用特定的 setter。但是当您不知道特定的绑定类时,就像在您的情况下一样,这就是要走的路。
您还可以使用类似的方法来编写可重用的 recyclerview 适配器。
| 归档时间: |
|
| 查看次数: |
2406 次 |
| 最近记录: |