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
}
这是 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()
    }
}
这是我拥有的片段的 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>
我不知道如何改变
lateinit var binding: FragmentHomeBinding
在 BaseFragment 中,它将是通用的,因为我需要用
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)  
编辑
玩弄这段代码后,我来到了这个:
基础片段:
    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()
}
首页片段
类 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
}
}
在这个解决方案中,我唯一不喜欢的是函数 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>
}
首页片段
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
    }
}
Oya*_*nlı 10
数据绑定类的通用类型是ViewDataBinding。所以你可以得到你的绑定实例:
val binding = DataBindingUtil.inflate<ViewDataBinding>(
                    inflater, getContentView(), container, false)
但是你不能像binding.viewModel那样设置viewModel,因为通用绑定类的实例不会有一个名为setViewModel的setter。你可以使用通用的 setVariable 来代替:
binding.setVariable(BR.viewModel, viewModel)
BR 是一个生成的类,其中包含您用于数据绑定的所有变量。上面的这个方法不是类型安全的,它不会检查提到的 BR 变量是否确实位于特定的绑定类中。这就是为什么通常最好使用特定的 setter。但是当您不知道特定的绑定类时,就像在您的情况下一样,这就是要走的路。
您还可以使用类似的方法来编写可重用的 recyclerview 适配器。
| 归档时间: | 
 | 
| 查看次数: | 2406 次 | 
| 最近记录: |