具有 2 种视图类型的 kotlin recyclerview

kotlin recyclerview with 2 view types

在每第 5 个项目之后,我想要加载一个按钮视图。

    override fun getItemViewType(position: Int): Int {

    if (position % 5 == 0 && position != 0) {
        return R.layout.button10th
    } else {
        return R.layout.checkitout
    }
}

当我 运行 它时,这是拿走了我的第 5 件物品。 我怎样才能在不拿走我的任何阵列项目的情况下实现这一目标?我需要修复我的 getItemCount 吗?

   override fun getItemCount(): Int {

    return lists.size
}

我希望它看起来像下面这样。 1.约翰 2.迈克 3.克里斯 4.简 5.起诉 6. 可点击按钮

在使用多视图 RecyclerViews 一段时间后,这是我最好的方法(它可以防止错误并且实现得当):

为您的两种项目类型创建两个 viewHolder classes,每个都有一个 bind() 函数:

class NameViewHolder(itemView: View) : 
     RecyclerView.ViewHolder(itemView) {

     fun bind(cell: Cell) {
          //Do your bindViewHolder logic in here
     }

}

由于我们有多个视图类型,因此有多个 viewHolder,我们需要创建一个普通的 java 对象来保存公共信息。我调用了我的 Cell()。称它为任何适合你的需要。我们会解决的。

所以现在你有两个 viewHolder classes:NameViewHolder()ButtonViewHolder().

现在让我们创建我们的 Cell class 和对象:

open class Cell {
    fun identifier() = this::class.java.name.hashCode()
}

class CellName(
    val name: String
) : Cell()

class CellButton(
    val buttonText: String
) : Cell()

让我解释一下:所以我创建了一个全局 Cell() 对象,其中有一个标识符函数,它给我一个 class 哈希。这个函数稍后会为我们服务,以获取我们的视图类型。在我的 RecyclerView 中,我不使用 Int 或其他东西来识别我的视图类型,而是使用我的对象 class 本身的散列。散列是每个 class 的唯一字符串。因此,如果我的适配器在其项目列表中偶然发现一个 CellName() 对象,则 recyclerView 使用我的 identifier() 函数获取其哈希值,并意识到此视图类型是名称,而不是按钮(来自您的示例多于)。 其他 classes 扩展了全局 Cell() class 并具有自定义的个人逻辑。给他们任何你喜欢或需要的参数。

现在在我们的适配器中,我们将像这样添加我们的单元格列表作为参数:

class MyAdapter(
   var items: ArrayList<Cell> = ArrayList()
): RecyclerView.Adapter<RecyclerView.ViewHolder>() {

确保完全按照上面的方式实现 RecyclerView.Adapter,否则多视图将无法工作。

现在,选择您的视图类型的 getItemViewType 覆盖方法将如下所示:

override fun getItemViewType(position: Int) = items[position].identifier()

如您所见,我们在这里使用我之前讲过的 identifier() 函数,让适配器知道根据 Cell() class 哈希选择什么视图类型。

现在是您的视图膨胀的 onCreateViewHolder:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 
     RecyclerView.ViewHolder {
        return when (viewType) {
           CellName::class.java.name.hashCode() -> 
                NameViewHolder(parent.inflate(R.layout.your_name_view)
           CellButton::class.java.name.hashCode() -> 
                ButtonViewHolder(parent.inflate(R.layout.your_button_view)
        }
     }

现在,当适配器找到名称视图类型时,它会使用所需的布局扩展 NameViewHolder,对于带有 ButtonViewHolder 的按钮也是如此。接下来,onBindViewHoder:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = items[holder.adapterPosition]) {
            is CellName -> (holder as NameViewHolder).bind(item)
            is CellButton -> (holder as ButtonViewHolder).bind(item)
         }}

基本上对于每种类型的单元格 class,您可以从 viewHolder classes.

访问绑定函数

recyclerView 适配器就是这样。这是到目前为止的整个文件(接下来我们将继续创建您的单元格列表):

class MyAdapter(
    private var items: ArrayList<Cell> = ArrayList()
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun getItemCount(): Int  = items.size
    override fun getItemViewType(position: Int) = items[position].identifier()

    @Suppress("HasPlatformType")
    fun ViewGroup.inflate(@LayoutRes resId: Int) = LayoutInflater.from(this.context)
        .inflate(resId, this, false)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
            RecyclerView.ViewHolder {
        return when (viewType) {
            CellName::class.java.name.hashCode() ->
                NameViewHolder(parent.inflate(R.layout.your_name_view))

            CellButton::class.java.name.hashCode() ->
                ButtonViewHolder(parent.inflate(R.layout.your_button_view))

        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = items[holder.adapterPosition]) {
            is CellName -> (holder as NameViewHolder)
                .bind(item)
            is CellButton -> (holder as ButtonViewHolder)
                .bind(item)
        }
    }
}

class NameViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(cell: Cell) {
        //Do your bindViewHolder logic in here
    }
}

class ButtonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(cell: Cell) {
        //Do your bindViewHolder logic in here
    }
}

open class Cell {
    fun identifier() = this::class.java.name.hashCode()
}

class CellName(
    val name: String
) : Cell()

class CellButton(
    val buttonText: String
) : Cell()

请确保滚动上面的文字,因为它太大了。此外,

@Suppress("HasPlatformType")
fun ViewGroup.inflate(@LayoutRes resId: Int) = LayoutInflater.from(this.context)
    .inflate(resId, this, false)

是一个 Kotlin 扩展函数,可以使布局 inflation 更快。按原样使用。

接下来,您的单元格列表创建:

fun createCells(
    // Whatever params.
): ArrayList(Cell){
    val cellList = ArrayList<Cell>()
    var firstCell = CellName("Christina")
    val secondCell = CellName("Mary")
    val thirdCell = CellButton("View More!")
    cellList.add(firstCell)
    cellList.add(secondCell)
    cellList.add(thirdCell)
    // Note that here you can do whatever you want, use forEach, for, anything to 
    create a full list of Cells with your desired information.
    return cellList
}

在任何 activity/fragment 有该适配器的地方使用此功能。

val myAdapter = MyAdapter(createCells())
recyclerView.adapter = myAdapter

就是这样。您可以根据需要随意自定义您的单元格和视图类型。请记住,对于您在 recyclerView 中需要的每个新视图类型,您必须为其创建一个单元格 class 和一个视图持有者 class。 这是我关于多种视图类型的完整教程。并回答您关于如何执行此操作的明确问题:

if (position % 5 == 0 && position != 0) {
    return R.layout.button10th
} else {
    return R.layout.checkitout
} //Your code from your question here

...你没有。在 recyclerView 中,您不再需要这种逻辑。适配器只能接收 Cells 列表,仅此而已。这只会防止大量错误并使您的代码更清晰,更易于阅读。您只需在 createCells() 函数中创建单元格,例如:

something.forEachIndexed {index, item ->
if(index % 5 == 0 && position != 0)
    cellList.add(CellButton("button_stuff"))
else
    cellList.add(CellName("blabla"))
}

您再也不用担心商品编号和位置了。只需使用 createCells() 函数来完成您的整个逻辑,但 return 一个完整的列表和适配器知道该做什么。

如果您想知道如何使用 bindViewHolder 中的 bind() 函数,您可以在您的适配器中执行您通常在该代码块中执行的任何操作,例如在 textView 和按钮中设置文本,设置使用 Glide 或通过资源链接创建您的按钮功能的图像。我实际上会解释如何实现按钮功能和 bind():

还记得我们是如何在我们的单元格对象中设置我们想要的信息的吗?

class ButtonViewHolder(itemView: View) : 
     RecyclerView.ViewHolder(itemView) {

     fun bind(cell: Cell) {
      //Do your bindViewHolder logic in here
     }
}

在这里您可以访问该对象,所以让我们创建一个按钮并为其添加一个回调。为此,您必须使用回调变量更新绑定函数。

fun bind(cell: Cell, buttonCallback: (() -> Unit)) {
    //Do your bindViewHolder logic in here

    itemView.yourXMLTitleWhatever.text = cell.titleText //(if you have a title for example)

    itemView.yourXMLButton.setOnClickListener {
         buttonCallback.invoke()
    }
}

调用函数告诉您的回调按钮已被按下。现在为了使回调工作,我们需要在您的适配器中将回调变量声明为 public。所以在你的适配器里面添加这个:

class MyAdapter(
    private var items: ArrayList<Cell> = ArrayList()
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var buttonCallback: (() -> Unit) = {}

(...)

并且不要忘记将 var 作为参数添加到适配器中的绑定调用中:

所以代替:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = items[holder.adapterPosition]) {
            is CellName -> (holder as NameViewHolder).bind(item)
            is CellButton -> (holder as ButtonViewHolder).bind(item)
         }}

我们将有:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = items[holder.adapterPosition]) {
            is CellName -> (holder as NameViewHolder).bind(item)
            is CellButton -> (holder as ButtonViewHolder).bind(item, buttonCallback)
         }}

基本上,这种类型的回调变量是接口的 Kotlin 快捷方式,当您创建接口来处理适配器点击时,它会做同样的事情,在 Java 中。

但我们还没有完成,请确保您的适配器回调变量不是私有的,并在您的 ACTIVITY 中执行此操作以访问它:

myAdapter.onButtonClick = { 
     //add your button click functionality here (like activity change or anything).
 }

其中 myAdapter = MyAdapter(cellList)

希望我有所帮助。

我发现实现这一点的最简单方法是将 modelViewHolder 相关联。我的意思是什么。

我的方法的工作原理是删除何时将每个 ViewHolder 从适配器填充到我们的元素列表中的决定。

假设我们想要显示一个名称列表,并且每第 5 个项目我们想要显示一个广告(只是一个例子,它可以是任何东西)

val elements = listOf("Name #1", "Name #2", "Name #3", "Name #4", AdModel("our ad"), "Name #5")

现在在适配器内部,我们首先说我们扩展了泛型ViewHolder class RecyclerView.Adapter<RecyclerView.ViewHolder>()

之后,我们要覆盖 3 个方法。

getItemViewType

在这里我们将决定哪个布局文件与每个 model 我们将使用实例检查和 return 我们要根据类型显示的布局文件来做到这一点。

override fun getItemViewType(position: Int): Int {
    val element = elements[position] // assuming your list is called "elements"

    return when (element) {
        is String -> R.layout.name_layout_file

        is AdModel -> R.layout.ad_layout_file

        else -> throw IllegalArgumentException("Unsupported type") // in case populated with a model we don't know how to display.
    }
}

onCreateViewHolder

您需要为每个要显示的类型创建一个 ViewHolder class。

 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val inflater = LayoutInflater.from(parent.context)

    return when (viewType) {
        R.layout.name_layout_file -> NameViewHolder(inflater.inflate(viewType, parent, false))

        R.layout.ad_layout_file -> AdViewHolder(inflater.inflate(viewType, parent, false))

        else -> throw IllegalArgumentException("Unsupported layout") // in case populated with a model we don't know how to display.
    }
}

onBindViewHolder

在这里我们可以很容易的施展我们的ViewHolder

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val element = elements[position] // assuming your list is called "elements"

    when (holder) {
        is NameViewHolder -> {
            val name = element as String
            // bind NameViewHolder
        }

        is AdViewHolder -> {
            val adModel = element as AdModel
            // bind AdViewHolder
        }

    }
}

您的完整适配器应如下所示:

class SampleAdapter(val elements: List<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        return when (viewType) {
            R.layout.name_layout_file -> NameViewHolder(inflater.inflate(viewType, parent, false))

            R.layout.ad_layout_file -> AdViewHolder(inflater.inflate(viewType, parent, false))

            else -> throw IllegalArgumentException("Unsupported layout") // in case populated with a model we don't know how to display.
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val element = elements[position] // assuming your list is called "elements"

        when (holder) {
            is NameViewHolder -> {
                val name = element as String
                // bind NameViewHolder
            }

            is AdViewHolder -> {
                val adModel = element as AdModel
                // bind AdViewHolder
            }

        }
    }

    override fun getItemViewType(position: Int): Int {
        val element = elements[position] // assuming your list is called "elements"

        return when (element) {
            is String -> R.layout.name_layout_file

            is AdModel -> R.layout.ad_layout_file

            else -> throw IllegalArgumentException("Unsupported type") // in case populated with a model we don't know how to display.
        }
    }
}

当您加载适配器时,您将其与多类型列表一起加载,一切正常。

最大的优点是你不局限于特定的类型,添加更多视图类型非常容易。

但是,如果您想保存所有这些工作,我编写了一个库,该库使用注释处理在编译时生成 single/multi-type 适配器。

该库会生成您需要的所有内容,包括 findViewById 并为您提供实现适配器所需编写的最少代码。

你可以在这里查看:Gencycler