android - 处理 recyclerview 适配器(Kotlin)中的按钮点击?

标签 android kotlin mvvm android-recyclerview

我有一个适配器,其中每个项目都有 3 个按钮,这些按钮生成一个对话框,然后执行一个操作。我觉得这应该从适配器中删除(我有可用的 View 模型),但它有效并且我想知道:我应该将逻辑移动到 fragment , View 模型,我是否需要移动它(下面的代码是不好的做法吗?如果是的话,为什么)?任何帮助/输入将不胜感激。

这是适配器代码:

class ViewRecipesAdapter(val context: Context, private val recipes: List<Recipe>, private val parentFragment: Fragment) :
        RecyclerView.Adapter<ViewRecipesAdapter.RecipeViewHolder>()
{

    private var listToUse: List<Recipe> = recipes
    private lateinit var recipesViewModel: RecipesViewModel
    private var isView = false


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder
    {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding: ViewRecipesItemBinding =
                DataBindingUtil.inflate(layoutInflater, R.layout.view_recipes_item, parent, false)

        return RecipeViewHolder(binding, context)
    }

    override fun getItemCount() = listToUse.size

    override fun onBindViewHolder(holder: RecipeViewHolder, position: Int)
    {

        val recipe = listToUse[position]
        // to delete and edit items
        val dao = RecipesDatabase.getInstance(context).recipeDao()

        val repository = RecipeRepository(dao)
        recipesViewModel = RecipesViewModel(repository)
        //display data on list item
        holder.bind(recipe)
        Glide.with(context).load(recipe.imageOne)
                .into(holder.binding.imageViewItemImage)
        //tried to handle clicks here through the viewModel but I could not get it working from fragment
        //the function call after viewModel calls is what works and it seems to work well
        holder.binding.imageButtonItemdelete.setOnClickListener {
            recipesViewModel.setIsDelete(true)
            recipesViewModel.setPositionFromAdapter(position)
            startDeleteDialog(position)
        }
        holder.binding.imageButtonItemedit.setOnClickListener {
            recipesViewModel.setIsView(false)
            recipesViewModel.setPositionFromAdapter(position)
            isView = false
            startEditOrViewDialog(position)
        }
        holder.binding.imageButtonItemview.setOnClickListener {
            recipesViewModel.setIsView(true)
            recipesViewModel.setPositionFromAdapter(position)
            isView = true
            startEditOrViewDialog(position)
        }

    }

    fun setList(newList: List<Recipe>)
    {
        listToUse = newList
    }

    //dialog functions for the edit, delete, and view buttons on each item
    private fun startDeleteDialog(position: Int)
    {
        AlertDialog.Builder(context)
                .setTitle("Delete recipe?")
                .setPositiveButton("Yes") { _, _ ->
                    recipesViewModel.deleteRecipe(recipes[position])
                    notifyItemRemoved(position)
                }
                .setNegativeButton("No") { dialog, _ ->
                    dialog.dismiss()
                }.show()
    }

    private fun startEditOrViewDialog(position: Int)
    {
        when (isView)
        {
            true ->
            {
                AlertDialog.Builder(context).setTitle("View recipe?")
                        .setPositiveButton("Yes") { _, _ ->
                            //get relevant data from current recipe
                            val recipe = recipes[position]
                            //create a dialog that shows this data in an inflated layout
                            val viewDialog = AlertDialog.Builder(context)
                            val inflater = LayoutInflater.from(context)
                            val view = inflater.inflate(R.layout.fragment_edit_or_view, null)

                            view.editText_editrecipe_directions.setText(recipe.directions)
                            view.editText_editrecipe_ingredients.setText(recipe.ingredients)
                            view.editText_editrecipe_notes.setText(recipe.notes)
                            view.editText_editrecipe_title.setText(recipe.title)
                            view.textView_date_edit.text = recipe.date
                            view.editText_editrecipe_title.keyListener = null
                            view.editText_editrecipe_directions.keyListener = null
                            view.editText_editrecipe_ingredients.keyListener = null
                            view.editText_editrecipe_notes.keyListener = null
                            if (recipe.rating != null)
                            {
                                view.ratingBar_edit.rating = recipe.rating
                            }
                            Glide.with(context)
                                    .load(recipe.imageOne)
                                    .into(view.imageView_addphoto_edit)
                            viewDialog.setView(view).show()
                        }
                        .setNegativeButton("No") { dialog, _ ->
                            dialog.dismiss()
                        }.show()
            }
            false ->
            {
                AlertDialog.Builder(context).setTitle("Edit recipe?")
                        .setPositiveButton("Yes") { _, _ ->
                            //get relevant data from current recipe
                            val recipe = recipes[position]
                            val idString = recipe.id.toString()
                            recipesViewModel.setId(idString)
                            recipesViewModel.getRecipeById2(idString)
                            notifyDataSetChanged()

                            val controller = parentFragment.findNavController()
                            controller.navigate(
                                ViewRecipesFragmentDirections.actionNavViewrecipesToNavAddrecipe(
                                    recipe.id.toString()
                                )
                            )
                        }
                        .setNegativeButton("No") { dialog, _ ->
                            dialog.dismiss()
                        }.show()
            }
        }
    }

    override fun getItemId(position: Int): Long
    {
        return position.toLong()
    }

    override fun getItemViewType(position: Int): Int
    {
        return position
    }


    class RecipeViewHolder(val binding: ViewRecipesItemBinding, val context: Context) :
            RecyclerView.ViewHolder(binding.root)
    {

        fun bind(recipe: Recipe)
        {
            if (recipe.isLeftover == true)
            {
                binding.tvIsLeftovers.visibility = View.VISIBLE
            }
            binding.textViewItemTitle.text = recipe.title

            if (recipe.date != null)
            {
                binding.textViewItemDate.text = recipe.date
            }
            if (recipe.rating != null)
            {
                binding.ratingBar2.rating = recipe.rating
            }
            binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
                duration = 1000
            }
        }
    }
}

这是 View 模型,设置了实时数据变量,我无法在这个 RecyclerView 所在的 fragment 中工作:

class RecipesViewModel(private val repository: RecipeRepository) : ViewModel()
{
    val recipesList = repository.getAllRecipes()

    private val _isView = MutableLiveData<Boolean>()
    val isView: MutableLiveData<Boolean> = _isView

    private val _isEdit = MutableLiveData<Boolean>()
    val isEdit: MutableLiveData<Boolean> = _isEdit

    private val _positionFromAdapter = MutableLiveData<Int>()
    val positionFromAdapter: MutableLiveData<Int> = _positionFromAdapter

    private val _isDelete = MutableLiveData<Boolean>()
    val isDelete: MutableLiveData<Boolean> = _isDelete

    private val _recipesListFromSearch = MutableLiveData<List<Recipe>>()
    val recipesListFromSearch: LiveData<List<Recipe>> = _recipesListFromSearch

    private val _recipe = MutableLiveData<Recipe>()

    val recipe: LiveData<Recipe> = _recipe

    lateinit var searchString: String

    val savedId = MutableLiveData<String>()

    fun setPositionFromAdapter(position: Int)
    {
        _positionFromAdapter.value = position
    }

    fun setIsView(isView: Boolean)
    {
        _isView.value = isView
    }

    fun setIsDelete(isDelete: Boolean)
    {
        _isView.value = isDelete
    }

    fun setIsEdit(isEdit: Boolean)
    {
        _isEdit.value = isEdit
    }

    fun setId(id: String)
    {
        savedId.value = id
    }

    fun insertRecipe(recipe: Recipe)
    {
        CoroutineScope(Dispatchers.IO).launch {
            repository.insertRecipe(recipe)
        }
    }

    fun getRecipesFromQuery(query: String)
    {
        CoroutineScope(Dispatchers.IO).launch {
            val list = repository.getRecipesSearch(query)
            MainScope().launch { _recipesListFromSearch.value = list }
        }
    }

    fun saveUserRecipeToDb(
        title: String?,
        ingredients: String?,
        directions: String?,
        notes: String?,
        uriToSave: String?,
        rating: Float?,
        date: String?,
        isLeftover: Boolean,
        loadedId: String
    ): Boolean
    {
        val recipeToSave = Recipe(
            title,
            ingredients,
            directions,
            notes,
            uriToSave,
            null,
            null,
            rating,
            date,
            isLeftover
        )
        if (loadedId != "666")
        {
            recipeToSave.id = loadedId.toInt()
        }
        insertRecipe(recipeToSave)
        return false
    }

    fun getRecipeById2(id: String) = repository.getRecipeByIdLive(id)

    fun deleteRecipe(recipe: Recipe)
    {
        CoroutineScope(Dispatchers.IO).launch {
            repository.deleteRecipe(recipe)
        }
    }
}

最佳答案

如何在 RecyclerView 中实现 onClick。假设在 Your Recycler 中,每个 View 都是某个 item 的可视化,当您单击它时,您想对该项目执行某些操作:

  1. 创建类:ClickListener:
class ClickListener(
    val clickListener: (itemId: Int) -> Unit,
)
{
    fun onClick(item: ItemClass) = clickListener(item.id)
}
  1. 现在在您的 RecylerViewAdapter 中将此 Listener 作为参数传递:
class RecylerViewAdapter(
    private val clickListener: ClickListener
)
  1. onBindViewHolder 中将此 Listenner 作为参数传递
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
    holder.bind(getItem(position)!!, clickListener)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
{
    return ViewHolder.from(
        parent
    )
}
  1. 在您的 ViewHolder 类中:
class ViewHolder private constructor(private val binding: ItemRecyclerBinding) :
        RecyclerView.ViewHolder(binding.root)
{
    companion object
    {
        fun from(parent: ViewGroup): ViewHolder
        {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = ItemRecyclerBinding.inflate(layoutInflater, parent, false)
            return ViewHolder(
                binding
            )
        }
    }


    fun bind(
        item : Item,
        clickListener: ClickListener
    )
    {
        binding.item = item
        binding.clickListener = clickListener
        binding.executePendingBindings()
    }
}
  1. 在您的项目布局(必须转换为数据绑定(bind)布局)中添加:
<data>
    <variable
        name="item"
        type="com.example.sth.database.Item" /> // path to `Item`
    <variable
        name="clickListener"
        type="com.example.sth.ui.adapter.ClickListener" /> // Path to `ClickListener`
</data>
  1. 现在你可以给 Button 添加 onClick 方法:
android:onClick="@{() -> clickListener.onClick(item)}"
  1. 当您在 fragment 或 Activity 中创建适配器时,您必须将 clickListenner 作为参数传递。通过这种方式,您可以处理 fragment 中的所有内容,而 RecyclerView 不关心您在此函数中做了什么。
val clickListenner = ClickListenner(
    { id -> viewModel.clickItemWithid(id) }, // click. This function from ViewModel will be executed when You click on item in recycler View
)

val adapter = RecylerViewAdapter (
    clickListenner
)

此方法基于 Udacity 上的 Google 开发人员代码实验室。
Here You can check whole codelabs. It is free .
And here is just one video with implementing click listenner

关于android - 处理 recyclerview 适配器(Kotlin)中的按钮点击?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63838818/

相关文章:

wpf - 在WPF中在 View / View 模型之间进行转换的可重用/很好的方式而无需任何代码隐藏

android - Google Play 控制台目标 SDK 级别 26 要求

java - 如何在方向更改时保存 ArrayLists 位置

java - 对于输入字符串: “Mat [ 0*0*CV_32FC1, isCont=true, isSubmat=false, nativeObj=0x78a0dff700, dataAddr=0x0 ]”-错误填充矩阵

android - 如何使用 Kotlin 声明 Pair 数组

kotlin - 为什么Rx会打印整个数组而不是每个项目

c# - 将 Windows 主题更改为高对比度时,将按钮颜色更改为白色

c# - 扩展选择模式、虚拟化和 IsSelected 绑定(bind)

android - CardView 自定义样式属性

java - 时间戳增加了额外的天数