Pass ViewHolder on bind to RecyclerView items instead of ViewBinding

This change lets items get the updated position of their view holder in the adapter. Fixes an issue where the position of items was not updated after being removed from a `SelectableGenericAdapter`.
This commit is contained in:
lynxnb 2022-07-31 01:56:34 +02:00 committed by Mark Collins
parent bb922100cb
commit 8991ccac65
13 changed files with 45 additions and 25 deletions

View File

@ -84,7 +84,8 @@ private typealias InteractionFunction = (appItem : AppItem) -> Unit
class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericListItem<LayoutBinding<*>>() { class AppViewItem(var layoutType : LayoutType, private val item : AppItem, private val missingIcon : Bitmap, private val onClick : InteractionFunction, private val onLongClick : InteractionFunction) : GenericListItem<LayoutBinding<*>>() {
override fun getViewBindingFactory() = LayoutBindingFactory(layoutType) override fun getViewBindingFactory() = LayoutBindingFactory(layoutType)
override fun bind(binding : LayoutBinding<*>, position : Int) { override fun bind(holder : GenericViewHolder<LayoutBinding<*>>, position : Int) {
val binding = holder.binding
binding.textTitle.text = item.title binding.textTitle.text = item.title
binding.textSubtitle.text = item.subTitle ?: item.loaderResultString(binding.root.context) binding.textSubtitle.text = item.subTitle ?: item.loaderResultString(binding.root.context)

View File

@ -46,7 +46,7 @@ open class GenericAdapter : RecyclerView.Adapter<GenericViewHolder<ViewBinding>>
override fun onBindViewHolder(holder : GenericViewHolder<ViewBinding>, position : Int) { override fun onBindViewHolder(holder : GenericViewHolder<ViewBinding>, position : Int) {
currentItems[position].apply { currentItems[position].apply {
adapter = this@GenericAdapter adapter = this@GenericAdapter
bind(holder.binding, position) bind(holder, position)
} }
} }

View File

@ -24,7 +24,7 @@ abstract class GenericListItem<V : ViewBinding> {
abstract fun getViewBindingFactory() : ViewBindingFactory abstract fun getViewBindingFactory() : ViewBindingFactory
abstract fun bind(binding : V, position : Int) abstract fun bind(holder : GenericViewHolder<V>, position : Int)
/** /**
* Used for filtering * Used for filtering

View File

@ -6,6 +6,7 @@
package emu.skyline.adapter package emu.skyline.adapter
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import emu.skyline.data.GpuDriverMetadata import emu.skyline.data.GpuDriverMetadata
import emu.skyline.databinding.GpuDriverItemBinding import emu.skyline.databinding.GpuDriverItemBinding
@ -18,12 +19,15 @@ open class GpuDriverViewItem(
var onDelete : ((position : Int, wasChecked : Boolean) -> Unit)? = null, var onDelete : ((position : Int, wasChecked : Boolean) -> Unit)? = null,
var onClick : (() -> Unit)? = null var onClick : (() -> Unit)? = null
) : SelectableGenericListItem<GpuDriverItemBinding>() { ) : SelectableGenericListItem<GpuDriverItemBinding>() {
private var position = -1 private var holder : GenericViewHolder<GpuDriverItemBinding>? = null
private val adapterPosition get() = holder?.adapterPosition ?: RecyclerView.NO_POSITION
override fun getViewBindingFactory() = GpuDriverBindingFactory override fun getViewBindingFactory() = GpuDriverBindingFactory
override fun bind(binding : GpuDriverItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<GpuDriverItemBinding>, position : Int) {
this.position = position this.holder = holder
val binding = holder.binding
binding.name.text = driverMetadata.name binding.name.text = driverMetadata.name
if (driverMetadata.packageVersion.isNotBlank() || driverMetadata.packageVersion.isNotBlank()) { if (driverMetadata.packageVersion.isNotBlank() || driverMetadata.packageVersion.isNotBlank()) {
@ -38,17 +42,21 @@ open class GpuDriverViewItem(
binding.radioButton.isChecked = position == selectableAdapter?.selectedPosition binding.radioButton.isChecked = position == selectableAdapter?.selectedPosition
binding.root.setOnClickListener { binding.root.setOnClickListener {
selectableAdapter?.selectAndNotify(position) selectableAdapter?.selectAndNotify(adapterPosition)
onClick?.invoke() onClick?.invoke()
} }
onDelete?.let { onDelete -> onDelete?.let { onDelete ->
binding.deleteButton.visibility = ViewGroup.VISIBLE binding.deleteButton.visibility = ViewGroup.VISIBLE
binding.deleteButton.setOnClickListener { binding.deleteButton.setOnClickListener {
val wasChecked = position == selectableAdapter?.selectedPosition val pos = adapterPosition
selectableAdapter?.removeItemAt(position) if (pos == RecyclerView.NO_POSITION)
return@setOnClickListener
onDelete.invoke(position, wasChecked) val wasChecked = pos == selectableAdapter?.selectedPosition
selectableAdapter?.removeItemAt(pos)
onDelete.invoke(pos, wasChecked)
} }
} ?: run { } ?: run {
binding.deleteButton.visibility = ViewGroup.GONE binding.deleteButton.visibility = ViewGroup.GONE

View File

@ -22,7 +22,8 @@ class HeaderRomFilterItem(private val formats : List<RomFormat>, selectedFormat
override fun getViewBindingFactory() = HeaderRomFilterBindingFactory override fun getViewBindingFactory() = HeaderRomFilterBindingFactory
override fun bind(binding : HeaderRomFilterBinding, position : Int) { override fun bind(holder : GenericViewHolder<HeaderRomFilterBinding>, position : Int) {
val binding = holder.binding
binding.chipGroup.removeViews(1, binding.chipGroup.childCount - 1) binding.chipGroup.removeViews(1, binding.chipGroup.childCount - 1)
for (format in formats) { for (format in formats) {
binding.chipGroup.addView(Chip(binding.root.context, null, R.attr.chipChoiceStyle).apply { text = format.name }) binding.chipGroup.addView(Chip(binding.root.context, null, R.attr.chipChoiceStyle).apply { text = format.name })

View File

@ -15,8 +15,8 @@ object HeaderBindingFactory : ViewBindingFactory {
class HeaderViewItem(private val text : String) : GenericListItem<SectionItemBinding>() { class HeaderViewItem(private val text : String) : GenericListItem<SectionItemBinding>() {
override fun getViewBindingFactory() = HeaderBindingFactory override fun getViewBindingFactory() = HeaderBindingFactory
override fun bind(binding : SectionItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<SectionItemBinding>, position : Int) {
binding.textTitle.text = text holder.binding.textTitle.text = text
} }
override fun toString() = "" override fun toString() = ""

View File

@ -6,6 +6,7 @@
package emu.skyline.adapter.controller package emu.skyline.adapter.controller
import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GenericViewHolder
import emu.skyline.databinding.ControllerItemBinding import emu.skyline.databinding.ControllerItemBinding
import emu.skyline.di.getInputManager import emu.skyline.di.getInputManager
import emu.skyline.input.ButtonGuestEvent import emu.skyline.input.ButtonGuestEvent
@ -15,12 +16,13 @@ import emu.skyline.input.ButtonId
* This item is used to display a particular [button] mapping for the controller * This item is used to display a particular [button] mapping for the controller
*/ */
class ControllerButtonViewItem(private val controllerId : Int, val button : ButtonId, private val onClick : (item : ControllerButtonViewItem, position : Int) -> Unit) : ControllerViewItem() { class ControllerButtonViewItem(private val controllerId : Int, val button : ButtonId, private val onClick : (item : ControllerButtonViewItem, position : Int) -> Unit) : ControllerViewItem() {
override fun bind(binding : ControllerItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
val binding = holder.binding
content = button.long?.let { binding.root.context.getString(it) } ?: button.toString() content = button.long?.let { binding.root.context.getString(it) } ?: button.toString()
val guestEvent = ButtonGuestEvent(controllerId, button) val guestEvent = ButtonGuestEvent(controllerId, button)
subContent = binding.root.context.getInputManager().eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: "" subContent = binding.root.context.getInputManager().eventMap.filter { it.value is ButtonGuestEvent && it.value == guestEvent }.keys.firstOrNull()?.toString() ?: ""
super.bind(binding, position) super.bind(holder, position)
binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }

View File

@ -8,6 +8,7 @@ package emu.skyline.adapter.controller
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isGone import androidx.core.view.isGone
import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GenericViewHolder
import emu.skyline.adapter.ViewBindingFactory import emu.skyline.adapter.ViewBindingFactory
import emu.skyline.adapter.inflater import emu.skyline.adapter.inflater
import emu.skyline.databinding.ControllerCheckboxItemBinding import emu.skyline.databinding.ControllerCheckboxItemBinding
@ -19,7 +20,8 @@ object ControllerCheckBoxBindingFactory : ViewBindingFactory {
class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericListItem<ControllerCheckboxItemBinding>() { class ControllerCheckBoxViewItem(var title : String, var summary : String, var checked : Boolean, private val onCheckedChange : (item : ControllerCheckBoxViewItem, position : Int) -> Unit) : GenericListItem<ControllerCheckboxItemBinding>() {
override fun getViewBindingFactory() = ControllerCheckBoxBindingFactory override fun getViewBindingFactory() = ControllerCheckBoxBindingFactory
override fun bind(binding : ControllerCheckboxItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerCheckboxItemBinding>, position : Int) {
val binding = holder.binding
binding.textTitle.isGone = title.isEmpty() binding.textTitle.isGone = title.isEmpty()
binding.textTitle.text = title binding.textTitle.text = title
binding.textSubtitle.isGone = summary.isEmpty() binding.textSubtitle.isGone = summary.isEmpty()

View File

@ -19,7 +19,8 @@ import emu.skyline.input.JoyConLeftController
* @param type The type of controller setting this item is displaying * @param type The type of controller setting this item is displaying
*/ */
class ControllerGeneralViewItem(private val controllerId : Int, val type : GeneralType, private val onClick : (item : ControllerGeneralViewItem, position : Int) -> Unit) : ControllerViewItem() { class ControllerGeneralViewItem(private val controllerId : Int, val type : GeneralType, private val onClick : (item : ControllerGeneralViewItem, position : Int) -> Unit) : ControllerViewItem() {
override fun bind(binding : ControllerItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
val binding = holder.binding
val context = binding.root.context val context = binding.root.context
val controller = context.getInputManager().controllers[controllerId]!! val controller = context.getInputManager().controllers[controllerId]!!
@ -38,7 +39,7 @@ class ControllerGeneralViewItem(private val controllerId : Int, val type : Gener
GeneralType.SetupGuide -> context.getString(R.string.setup_guide_description) GeneralType.SetupGuide -> context.getString(R.string.setup_guide_description)
} }
super.bind(binding, position) super.bind(holder, position)
binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }

View File

@ -2,6 +2,7 @@ package emu.skyline.adapter.controller
import android.view.ViewGroup import android.view.ViewGroup
import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GenericViewHolder
import emu.skyline.adapter.ViewBindingFactory import emu.skyline.adapter.ViewBindingFactory
import emu.skyline.adapter.inflater import emu.skyline.adapter.inflater
import emu.skyline.databinding.ControllerHeaderBinding import emu.skyline.databinding.ControllerHeaderBinding
@ -13,8 +14,8 @@ object ControllerHeaderBindingFactory : ViewBindingFactory {
class ControllerHeaderItem(private val text : String) : GenericListItem<ControllerHeaderBinding>() { class ControllerHeaderItem(private val text : String) : GenericListItem<ControllerHeaderBinding>() {
override fun getViewBindingFactory() = ControllerHeaderBindingFactory override fun getViewBindingFactory() = ControllerHeaderBindingFactory
override fun bind(binding : ControllerHeaderBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerHeaderBinding>, position : Int) {
binding.root.text = text holder.binding.root.text = text
} }
override fun areItemsTheSame(other : GenericListItem<ControllerHeaderBinding>) = other is ControllerHeaderItem override fun areItemsTheSame(other : GenericListItem<ControllerHeaderBinding>) = other is ControllerHeaderItem

View File

@ -18,7 +18,8 @@ import emu.skyline.input.StickId
* This item is used to display all information regarding a [stick] and it's mappings for the controller * This item is used to display all information regarding a [stick] and it's mappings for the controller
*/ */
class ControllerStickViewItem(private val controllerId : Int, val stick : StickId, private val onClick : (item : ControllerStickViewItem, position : Int) -> Unit) : ControllerViewItem(stick.toString()) { class ControllerStickViewItem(private val controllerId : Int, val stick : StickId, private val onClick : (item : ControllerStickViewItem, position : Int) -> Unit) : ControllerViewItem(stick.toString()) {
override fun bind(binding : ControllerItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
val binding = holder.binding
val context = binding.root.context val context = binding.root.context
val inputManager = context.getInputManager() val inputManager = context.getInputManager()
@ -39,7 +40,7 @@ class ControllerStickViewItem(private val controllerId : Int, val stick : StickI
subContent = "${context.getString(R.string.button)}: $button\n${context.getString(R.string.up)}: $yAxisPlus\n${context.getString(R.string.down)}: $yAxisMinus\n${context.getString(R.string.left)}: $xAxisMinus\n${context.getString(R.string.right)}: $xAxisPlus" subContent = "${context.getString(R.string.button)}: $button\n${context.getString(R.string.up)}: $yAxisPlus\n${context.getString(R.string.down)}: $yAxisMinus\n${context.getString(R.string.left)}: $xAxisMinus\n${context.getString(R.string.right)}: $xAxisPlus"
super.bind(binding, position) super.bind(holder, position)
binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }

View File

@ -15,13 +15,14 @@ import emu.skyline.input.ControllerType
* This item is used to display the [type] of the currently active controller * This item is used to display the [type] of the currently active controller
*/ */
class ControllerTypeViewItem(private val type : ControllerType, private val onClick : (item : ControllerTypeViewItem, position : Int) -> Unit) : ControllerViewItem() { class ControllerTypeViewItem(private val type : ControllerType, private val onClick : (item : ControllerTypeViewItem, position : Int) -> Unit) : ControllerViewItem() {
override fun bind(binding : ControllerItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
val binding = holder.binding
val context = binding.root.context val context = binding.root.context
content = context.getString(R.string.controller_type) content = context.getString(R.string.controller_type)
subContent = context.getString(type.stringRes) subContent = context.getString(type.stringRes)
super.bind(binding, position) super.bind(holder, position)
binding.root.setOnClickListener { onClick.invoke(this, position) } binding.root.setOnClickListener { onClick.invoke(this, position) }
} }

View File

@ -8,6 +8,7 @@ package emu.skyline.adapter.controller
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isGone import androidx.core.view.isGone
import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.GenericViewHolder
import emu.skyline.adapter.ViewBindingFactory import emu.skyline.adapter.ViewBindingFactory
import emu.skyline.adapter.inflater import emu.skyline.adapter.inflater
import emu.skyline.databinding.ControllerItemBinding import emu.skyline.databinding.ControllerItemBinding
@ -21,8 +22,9 @@ open class ControllerViewItem(var content : String = "", var subContent : String
override fun getViewBindingFactory() = ControllerBindingFactory override fun getViewBindingFactory() = ControllerBindingFactory
override fun bind(binding : ControllerItemBinding, position : Int) { override fun bind(holder : GenericViewHolder<ControllerItemBinding>, position : Int) {
this.position = position this.position = position
val binding = holder.binding
binding.textTitle.apply { binding.textTitle.apply {
isGone = content.isEmpty() isGone = content.isEmpty()
text = content text = content