【问题标题】:Handling button clicks in recyclerview adapter (Kotlin)?处理recyclerview适配器(Kotlin)中的按钮点击?
【发布时间】:2020-12-29 11:46:20
【问题描述】:

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

这是适配器代码:

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
            }
        }
    }
}

这是视图模型,设置了实时数据变量,我无法在此 RecyclerView 所在的片段中工作:

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)
        }
    }
}

【问题讨论】:

    标签: android kotlin mvvm android-recyclerview


    【解决方案1】:

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

    1. 创建类:ClickListener:
    class ClickListener(
        val clickListener: (itemId: Int) -> Unit,
    )
    {
        fun onClick(item: ItemClass) = clickListener(item.id)
    }
    
    1. 现在在您的 RecylerViewAdapter 中将这个监听器作为参数传递:
    class RecylerViewAdapter(
        private val clickListener: ClickListener
    )
    
    1. onBindViewHolder 中将此监听器作为参数传递
    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. 在您的项目布局(必须转换为数据绑定布局)中添加:
    <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. 现在您可以将onClick 方法添加到Button:
    android:onClick="@{() -> clickListener.onClick(item)}"
    
    1. 当您在片段或活动中创建适配器时,您必须将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

    【讨论】:

    • 我一直在尝试为每个项目中的 3 个单独的图像按钮进行这项工作,但我不确定如何使用此示例来区分它们。只需单击一下即可查看整个视图,而我正在寻找每个视图内部的按钮单击。我确实需要使用配方 ID 来删除/编辑数据库,但还需要根据单击的按钮显示单独的对话框。您将如何修改它以适应这种需求?
    • 您的 Click 监听器必须有 3 个函数 click1、click2、click3。现在你必须在构造函数中传递它们。然后在bind 函数中,您没有为整个视图设置clickListenner,而只是为一个ImageView 设置。如果你做不到,告诉我,我会为你写完整的项目
    【解决方案2】:

    这些是现在对我有用的更改:

        class ClickListener(val clickListener: (itemId: Int, itemPosition: Int, dialogInt: Int) -> Unit) {
    fun onClickDelete(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
    fun onClickEdit(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
    fun onClickView(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
    }
    

    在适配器中:

    class RecipeViewHolder private constructor(val binding: ViewRecipesItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
    
        companion object {
            fun from(parent: ViewGroup): RecipeViewHolder{
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ViewRecipesItemBinding.inflate(layoutInflater, parent, false)
                return RecipeViewHolder(binding)
            }
    
        }
            fun bind(recipe: Recipe, clickListener: ClickListener) {
                binding.recipe = recipe
                binding.imageButtonItemdelete.setOnClickListener {
                    clickListener.onClickDelete(recipe, adapterPosition, 1)
                }
                binding.imageButtonItemedit.setOnClickListener {
                    clickListener.onClickEdit(recipe, adapterPosition,2)
                }
                binding.imageButtonItemview.setOnClickListener {
                    clickListener.onClickView(recipe, adapterPosition,3)
                }
                binding.executePendingBindings()
    
            binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
                duration = 1000
            }
        }
    

    在持有 RecyclerView 的片段中:

       private fun initRecyclerView() {
        recipesViewModel.recipesList.observe(viewLifecycleOwner, Observer {
          //update recyclerview
            val list = it
            listForFragment = it
            clickListener = ClickListener { id, position, dialogInt ->
                recipesViewModel.apply {
                    setPositionFromAdapter(position)
                    setDialogRecipe(id)
                }
                when (dialogInt) {
                    1 -> startDeleteDialog(position)
                    2 -> startEditDialog(position)
                    3 -> startViewDialog(position)
                }
            }
            rv_viewrecipes.adapter = ViewRecipesAdapter(requireContext(), list, this, clickListener)
    
            rv_viewrecipes.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
        })
    }
    

    我不知道如何通过 xml onClick 为 when 语句传递位置和 int,但现在至少适配器类已删除视图逻辑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-06-18
      • 1970-01-01
      • 2019-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-20
      相关资源
      最近更新 更多