Swipe en un RecyclerView
Cómo añadir swipe a un RecyclerView
Uso de swipe
Crear una nueva rama llamada feature/swipe en el proyecto RecyclerViewExample:
git checkout master
git checkout -b feature/swipe
Ficheros del proyecto
res/layout/item_superhero.xml
<?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_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ivSuperHero"
android:layout_width="150dp"
android:layout_height="150dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_launcher_background" />
<TextView
android:id="@+id/tvSuperHeroName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="21sp"
android:textStyle="bold"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivSuperHero"
app:layout_constraintTop_toTopOf="parent"
tools:text="KotlinMan" />
<TextView
android:id="@+id/tvRealName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivSuperHero"
app:layout_constraintTop_toBottomOf="@+id/tvSuperHeroName"
tools:text="Julio" />
<TextView
android:id="@+id/tvPublisher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="Hola" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
res/layout/acitvity_main.xml
<?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/recyclerSuperHero"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.03" />
</androidx.constraintlayout.widget.ConstraintLayout>
adapter/SuperHeroViewHolder.kt
class SuperHeroViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = ItemSuperheroBinding.bind(view)
fun render(superHeroModel: SuperHero, onClickListener: (SuperHero) -> Unit) {
binding.tvSuperHeroName.text = superHeroModel.superhero
binding.tvRealName.text = superHeroModel.realName
binding.tvPublisher.text = superHeroModel.publisher
// Glide.with(binding.ivSuperHero.context).load(superHeroModel.photo).into(binding.ivSuperHero)
Picasso.get().load(superHeroModel.photo).into(binding.ivSuperHero);
itemView.setOnClickListener {
onClickListener(superHeroModel)
}
}
}
adapter/SuperHeroAdapter.kt
class SuperHeroAdapter(
private val superheroList: List<SuperHero>,
private val onClickListener: (SuperHero) -> Unit
) : RecyclerView.Adapter<SuperHeroViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuperHeroViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return SuperHeroViewHolder(layoutInflater.inflate(R.layout.item_superhero, parent, false))
}
override fun onBindViewHolder(holder: SuperHeroViewHolder, position: Int) {
val item = superheroList[position]
holder.render(item, onClickListener)
}
override fun getItemCount(): Int {
return superheroList.size
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var superHeroMutableList: MutableList<SuperHero> = SuperHeroProvider.superHeroList.toMutableList()
private lateinit var adapter: SuperHeroAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initRecyclerView()
}
private fun initRecyclerView() {
val manager = LinearLayoutManager(this)
val decoration = DividerItemDecoration(this, manager.orientation)
binding.recyclerSuperHero.layoutManager = manager
adapter =
SuperHeroAdapter(superHeroMutableList,
{ superHero ->onItemSelected(superHero)}
)
binding.recyclerSuperHero.adapter = adapter
binding.recyclerSuperHero.addItemDecoration(decoration)
//right swipe on recyclerview
val itemSwipe = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
//TODO("Not yet implemented")
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder)
}
}
val swipe = ItemTouchHelper(itemSwipe)
swipe.attachToRecyclerView(binding.recyclerSuperHero)
}
private fun showDialog(viewHolder: RecyclerView.ViewHolder) {
val builder = AlertDialog.Builder(this)
builder.setTitle("Delete Item")
builder.setMessage("Are you sure to delete the item?")
builder.setPositiveButton("Confirm") {dialog, which ->
// val position = viewHolder.adapterPosition
val position = viewHolder.getBindingAdapterPosition()
superHeroMutableList.removeAt(position)
// adapter.notifyItemRemoved(position)
adapter?.notifyDataSetChanged()
}
builder.setNegativeButton("Cancel") {dialog, which ->
// val position = viewHolder.adapterPosition
val position = viewHolder.getBindingAdapterPosition()
adapter?.notifyItemChanged(position)
}
builder.show()
}
fun onItemSelected(superHero: SuperHero) {
Toast.makeText(this, superHero.superhero, Toast.LENGTH_SHORT).show()
}
}
Uso de Material Design en showDialog()
Pregunta a Gemini: how can I improve the dialog fun to use material design 3?
// import com.google.android.material.dialog.MaterialAlertDialogBuilder
private fun showDialog(viewHolder: RecyclerView.ViewHolder) {
// Use MaterialAlertDialogBuilder for Material 3 styling
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle("Delete Item")
builder.setMessage("Are you sure you want to delete this item?")
builder.setPositiveButton("Confirm") { dialog, which ->
val position = viewHolder.bindingAdapterPosition
// Check if the position is valid before removing
if (position != RecyclerView.NO_POSITION) {
superHeroMutableList.removeAt(position)
adapter.notifyItemRemoved(position)
}
}
builder.setNegativeButton("Cancel") { dialog, which ->
val position = viewHolder.bindingAdapterPosition
// Check if the position is valid before notifying the adapter
if (position != RecyclerView.NO_POSITION) {
// Restore the item's appearance if the user cancels the swipe
adapter.notifyItemChanged(position)
}
}
// It's good practice to ensure the dialog is not cancelable by tapping outside
// for this type of confirmation, to prevent accidental dismissal.
builder.setCancelable(false)
builder.show()
}
Key Improvements in This Code:
1.Material 3 Dialog:
By switching to MaterialAlertDialogBuilder, the dialog (including buttons, typography, and shape) will automatically adopt the Material You styling from your app’s theme.
2.More Efficient RecyclerView Updates:
I’ve replaced notifyDataSetChanged() with notifyItemRemoved(position). This is much more efficient because it animates the removal of a single item instead of redrawing the entire list.
In the «Cancel» block, notifyItemChanged(position) is used to correctly reset the state of the swiped item if the user decides not to delete it.
3.Safer Position Handling:
I’ve changed getBindingAdapterPosition() to the property bindingAdapterPosition.
I’ve added a check (position != RecyclerView.NO_POSITION) to prevent potential crashes if the view holder’s position becomes invalid while the dialog is open.

Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.