如何用Kotlin正确地处理RecyclerView的点击量

423 阅读2分钟

使用Kotlin正确处理RecyclerView的点击量

许多开发者倾向于以错误的方式处理RecyclerView 上的点击。在本教程中,我们将重点介绍处理RecyclerView 上的点击的最合适的方式。

前提条件

要跟上本教程,读者应该。

  • 知道如何在Android Studio 中创建一个项目。
  • Kotlin 有一定的了解。
  • 使用过基本的RecyclerView ,以显示来自API的数据(我们将从REST API获取数据)。
  • 知道Retrofit是什么以及如何使用它。
  • 对Jetpack库有一定的了解,如ViewModelLiveData
  • 熟悉ViewBinding

目标

在本教程结束时,读者应该拥有。

  • 概述了什么是RecyclerView
  • 了解了ListAdapterDiffUtil
  • 有能力处理RecyclerView 中的点击。

简介

RecyclerView 是一个显示数据项列表的伟大部件。RecyclerView 类支持显示数据的集合。

什么是ListAdapter?

根据官方文档。ListAdapter是一个RecyclerView.Adapter基类,用于在RecyclerView中显示List数据,包括在后台线程中计算Lists之间的差异。

当使用LiveData<List> ,ListAdapter提供了一个简单的方法来提供数据给适配器。当有新数据时,你可以使用submitList(POJO)。它可以处理项目的添加和删除,而不需要重新绘制整个视图。它还能将这些变化变成动画。

什么是DiffUtil?

根据官方文档。DiffUtil是一个实用类,它计算两个列表之间的差异,并输出一个更新操作的列表,将第一个列表转换为第二个列表。

这个类消除了调用notifyDataSetChanged() 方法的需要。我们覆盖了它的两个方法;areItemsTheSame(oldItem: Pojo, newItem: Pojo)areContentsTheSame(oldItem: Pojo, newItem: Pojo)

第一个方法检查这两个对象是否相同(例如,基于id)。而第二个检查两个对象之间的数据是否相同。DiffUtil 使得ListAdapter 可以改变列表中的项目。

我们将从api.imgflip.com/get_memesAPI中获取memes。然后在一个RecyclerView 中显示它们,并添加一个点击监听器来处理对每一行的点击。

第1步:创建项目

在这一步,我们将创建一个新的项目,你可以参考下面的图片进行指导。

create_project

第2步:添加依赖性

创建项目后,我们将添加一些依赖项到我们的应用级构建Gradle。

Retrofit 将帮助我们对API进行网络调用。 将发挥将JSON字符串转换为Java对象的作用。GsonConverter

// Retrofit and Gson Converter for Networking
  implementation 'com.squareup.retrofit2:retrofit:2.9.0'
  implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

  // Glide for Image Loading
  implementation 'com.github.bumptech.glide:glide:4.12.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

  // ViewModels and LiveData
  def lifecycle_version = "2.3.1"
  implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
  implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
  kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

请确保在应用级构建中也启用ViewBinding 。gradlegradle buildFeatures{ viewBinding true }

ViewBinding 生成一个Java类,代替代码中的findViewById。

第3步:XML布局

在这一步,我们将为回收器行和主布局创建布局。

首先,我们将设计Recycler行,它将有一个ImageView ,显示备忘录的照片,也有一个TextView ,显示备忘录的名称。接下来,我们将设计主要活动。它将有一个RecyclerView ,一个ProgressBar ,和一个TextView ,当我们的API调用失败时将显示一个错误。

回收器行布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_margin="5dp"
 app:cardElevation="5dp"
 app:cardCornerRadius="15dp">

 <androidx.constraintlayout.widget.ConstraintLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <androidx.appcompat.widget.AppCompatImageView
 android:id="@+id/imageView"
 android:layout_width="0dp"
 android:layout_height="200dp"
 android:scaleType="centerInside"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toTopOf="parent"
 tools:srcCompat="@tools:sample/backgrounds/scenic" />

 <TextView
 android:id="@+id/memeName"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_marginTop="16dp"
 android:layout_marginBottom="8dp"
 android:textAppearance="@style/TextAppearance.AppCompat.Medium"
 android:textStyle="bold"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toEndOf="@+id/imageView"
 app:layout_constraintStart_toStartOf="@+id/imageView"
 app:layout_constraintTop_toBottomOf="@+id/imageView"
 tools:text="Drake Hotline Bling" />

 </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
主布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".MainActivity">

 <androidx.recyclerview.widget.RecyclerView
 android:id="@+id/recyclerView"
 android:layout_width="0dp"
 android:layout_height="0dp"
 app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toTopOf="parent"
 tools:listitem="@layout/recycler_row" />

 <ProgressBar
 android:id="@+id/progressBar"
 style="?android:attr/progressBarStyle"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:visibility="gone"
 app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toTopOf="parent" />

 <TextView
 android:id="@+id/textViewFailed"
 android:layout_width="0dp"
 android:layout_height="wrap_content"
 android:gravity="center_horizontal"
 android:textAlignment="center"
 android:visibility="gone"
 app:layout_constraintBottom_toBottomOf="parent"
 app:layout_constraintEnd_toEndOf="parent"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toTopOf="@+id/recyclerView"
 tools:text="TextView" />
</androidx.constraintlayout.widget.ConstraintLayout>

第4步:模型类

在这一步中,我们将提出我们的模型类,我们只对备忘录的名称(name )和备忘录的图片(url )感兴趣。现在,我们将忽略其他的属性。

package com.kanyideveloper.recyclerviewitemclicksdemo

import com.google.gson.annotations.SerializedName

data class Memes(
    @SerializedName("data")
    val `data`: Data?,
    @SerializedName("success")
    val success: Boolean?
)

data class Data(
    @SerializedName("memes")
    val memes: List<Meme?>?
)

data class Meme(
    @SerializedName("box_count")
    val boxCount: Int?,
    @SerializedName("height")
    val height: Int?,
    @SerializedName("id")
    val id: String?,
    @SerializedName("name")
    val name: String?,
    @SerializedName("url")
    val url: String?,
    @SerializedName("width")
    val width: Int?
)

第5步:Api服务类

在这一步,我们将设计我们的ApiService,以便与Retrofit进行API调用。我们的基本URL将是https://api.imgflip.com/ ,端点将是get_memes

package com.kanyideveloper.recyclerviewitemclicksdemo

import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET

interface ApiService {
    @GET("get_memes")
    fun getPhotos(): Call<Memes>
}

object MemesApi {
    private const val BASE_URL = "https://api.imgflip.com/"

    private val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val apiService: ApiService by lazy {
        retrofit.create(ApiService::class.java)
    }
}

第6步:RecyclerView适配器类

在这一步,我们将创建我们的适配器类,它将完成所有的点击操作。

首先,我们创建一个名为OnClickListener 的类,在其构造函数中以一个meme项目作为参数,接收一个lambda。这个类包含一个名为onClick 的匹配函数。它将被设置为lambda的参数。所有这些创建了一种命名的lambda。

class OnClickListener(val clickListener: (meme: Meme) -> Unit) {
    fun onClick(meme: Meme) = clickListener(meme)
}

然后我们在MemesAdapter 的构造函数上添加了一个onClickListener 属性。

class MemesAdapter(private val onClickListener: OnClickListener) :
    ListAdapter<Meme, MemesAdapter.MyViewHolder>(MyDiffUtil)

最后,在这个类中,我们将通过在onBindViewHolder 内调用我们的onClickListener 来完成。

holder.itemView.setOnClickListener {
            onClickListener.onClick(meme)
}

下面是整个类的实现。

package com.kanyideveloper.recyclerviewitemclicksdemo

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.kanyideveloper.recyclerviewitemclicksdemo.databinding.RecyclerRowBinding

class MemesAdapter(private val onClickListener: OnClickListener) :
    ListAdapter<Meme, MemesAdapter.MyViewHolder>(MyDiffUtil) {

    companion object MyDiffUtil : DiffUtil.ItemCallback<Meme>() {
        override fun areItemsTheSame(oldItem: Meme, newItem: Meme): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: Meme, newItem: Meme): Boolean {
            return oldItem.id == newItem.id
        }
    }

    inner class MyViewHolder(private val binding: RecyclerRowBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(meme: Meme?) {
            Glide.with(binding.imageView)
                .load(meme?.url)
                .into(binding.imageView)

            binding.memeName.text = meme?.name
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(
            RecyclerRowBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val meme = getItem(position)
        holder.itemView.setOnClickListener {
            onClickListener.onClick(meme)
        }
        holder.bind(meme)
    }

    class OnClickListener(val clickListener: (meme: Meme) -> Unit) {
        fun onClick(meme: Meme) = clickListener(meme)
    }
}

第7步:ViewModel类

接下来,我们将创建一个ViewModel类,它将包含能在配置变化中存活的代码,即屏幕旋转。特别是,它有一个函数,将执行对API的网络请求。

package com.kanyideveloper.recyclerviewitemclicksdemo

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainViewModel : ViewModel() {

    private val _reponse = MutableLiveData<Memes>()
    val response: LiveData<Memes>
        get() = _response

    private val _loading = MutableLiveData<Boolean>()
    val loading: LiveData<Boolean>
        get() = _loading

    private val _failed = MutableLiveData<String>()
    val failed: LiveData<String>
        get() = _failed

    init {
        _loading.value = true
        getApiResponse()
    }

    private fun getApiResponse() {
        MemesApi.apiService.getPhotos().enqueue(object : Callback<Memes> {
            override fun onResponse(call: Call<Memes>, response: Response<Memes>) {
                _response.value = response.body()
                _loading.value = false
            }

            override fun onFailure(call: Call<Memes>, t: Throwable) {
                _loading.value = false
                _failed.value = t.localizedMessage
            }
        })
    }
}

第8步:总结MainActivity

在这最后一步,我们将通过向MemesAdapter添加OnClickListener 对象来实例化这个适配器。这将从适配器中返回一个meme。

现在,我们可以尝试对点击的meme的名字进行Toast。在其他情况下,可能需要移动到另一个活动或片段,并显示被点击项目的细节。

package com.kanyideveloper.recyclerviewitemclicksdemo

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.kanyideveloper.recyclerviewitemclicksdemo.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val viewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
    private lateinit var adapter: MemesAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        adapter = MemesAdapter(MemesAdapter.OnClickListener { photo ->
Toast.makeText(applicationContext, "${photo.name}", Toast.LENGTH_SHORT).show() })

        viewModel.response.observe(this, Observer { meme ->
            val list = meme.data?.memes
            adapter.submitList(list)
            binding.recyclerView.adapter = adapter
        })

        viewModel.loading.observe(this, Observer { loading ->
            binding.progressBar.isVisible = loading
        })

        viewModel.failed.observe(this, Observer { failed ->
            binding.textViewFailed.text = failed
            binding.textViewFailed.isVisible = true
        })
    }
}
adapter = MemesAdapter(MemesAdapter.OnClickListener { photo ->
           Toast.makeText(applicationContext, "${photo.name}", Toast.LENGTH_SHORT).show()
 })

演示屏幕

一旦完成,运行该应用程序。以下是你应该期待的。

recyclerview

recyclerview_clicked

GitHub上查看整个项目。

结论

这还不是关于Recyclerviews 点击的全部,请继续探索。通过这篇文章,我希望你已经了解了如何处理你的Recyclerviews 上的点击。