I have a set of overlapping cards like this Overlapped cards
So what I want to achieve is When I swipe LEFT the front card (A) it should move to bottom of all the overlapped cards and the card B below it should become the front card and so on. So in this way eventually, the card A will reappear again when it’s position comes. Below is my fragment code
class ToLearnFlashCards : Fragment() {
private lateinit var binding: FragmentToLearnFlashCardsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentToLearnFlashCardsBinding.inflate(layoutInflater, container, false)
val layoutManager = OverlappingLayoutManager(requireContext())
binding.recyclerview.layoutManager = layoutManager
val adapter = ToLearnAdapter { cardPair ->
cardViewmodel.deleteCards(cardPair)
}
binding.recyclerview.adapter = adapter
binding.recyclerview.itemAnimator = null
// Swipe Gesture
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
val itemView = viewHolder.itemView
if (dX > 0) {
itemView.pivotX = itemView.width.toFloat()
itemView.pivotY = itemView.height.toFloat() / 2
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
val cardPair = adapter.currentList[position]
when (direction) {
ItemTouchHelper.RIGHT -> {
//Some other logic here
}
ItemTouchHelper.LEFT -> {
adapter.moveItemToEnd(position)
}
}
}
})
itemTouchHelper.attachToRecyclerView(binding.recyclerview)
return binding.root
}
}
Below is the code for my Adapter class of it
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.visuallithuanian.R
import com.example.visuallithuanian.database.FlashcardPair
class ToLearnAdapter(
private val onDeleteListener: (FlashcardPair) -> Unit
) : ListAdapter<FlashcardPair, ToLearnAdapter.WordViewHolder>. (WordsComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycle_view, parent, false)
return WordViewHolder(view)
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current)
}
inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val wordEnglish: TextView = itemView.findViewById(R.id.textView1English)
private val wordLithuanian: TextView = itemView.findViewById(R.id.textView2Lithuanian)
fun bind(item: FlashcardPair) {
wordEnglish.text = item.front
wordLithuanian.text = item.back
}
}
fun moveItemToEnd(position: Int) {
val currentList = currentList.toMutableList()
val item = currentList.removeAt(position)
currentList.add(item)
submitList(currentList)
}
class WordsComparator : DiffUtil.ItemCallback<FlashcardPair>() {
override fun areItemsTheSame(oldItem: FlashcardPair, newItem: FlashcardPair): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: FlashcardPair, newItem: FlashcardPair): Boolean {
return oldItem.front == newItem.front && oldItem.back == newItem.back
}
}
}
Below is my customised OverlappingLayoutManager.kt
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
import android.view.animation.LinearInterpolator
import androidx.annotation.Dimension
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import com.example.visuallithuanian.R
class OverlappingLayoutManager(context: Context) : RecyclerView.LayoutManager() {
private val horizontalOverlap: Float =
context.resources.getDimension(R.dimen.card_horizontal_overlap)
private val verticalOverlap: Float =
context.resources.getDimension(R.dimen.card_vertical_overlap)
@get:Dimension
private val tiltAngle: Float =
context.resources.getDimension(R.dimen.card_tilt_angle)
private var interpolator: Interpolator = LinearInterpolator()
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
return RecyclerView.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State?) {
if (itemCount == 0) {
removeAndRecycleAllViews(recycler)
return
}
detachAndScrapAttachedViews(recycler)
val parentWidth = width
val parentHeight = height
var zIndex = 0
for (position in 0 until itemCount) {
val view = recycler.getViewForPosition(position)
addView(view)
// Set z-index for the view
val elevation = zIndex.toFloat()
ViewCompat.setElevation(view, elevation)
zIndex++
measureChildWithMargins(view, 0, 0)
val width = getDecoratedMeasuredWidth(view)
val height = getDecoratedMeasuredHeight(view)
val centerX = parentWidth / 2f
val centerY = parentHeight / 2f
val itemOffset = calculateItemOffset(position)
val newCenterX = centerX + itemOffset * horizontalOverlap
val left = (newCenterX - (width / 2)).toInt()
val top = (centerY - (height / 2)).toInt()
val right = (newCenterX + (width / 2)).toInt()
val bottom = (centerY + (height / 2)).toInt()
layoutDecoratedWithMargins(view, left, top, right, bottom)
applyTilt(view, position)
}
}
private fun applyTilt(view: View, position: Int) {
val tilt = calculateTilt(position) * tiltAngle
view.pivotX = (view.width / 2).toFloat()
view.pivotY = (view.height / 2).toFloat()
view.rotation = tilt
}
protected open fun calculateItemOffset(position: Int): Float {
return if (position % 2 == 0) -1f else 1f
}
protected open fun calculateTilt(position: Int): Float {
return if (position % 2 == 0) -1f else 1f
}
fun setInterpolator(interpolator: Interpolator) {
this.interpolator = interpolator
}
}
So I have created a function called moveItemToEnd() with a paramater position in it in My Adapter class.So i was expecting that when i swipe LEFT my front card, it should go at the bottom of all the overlapped cards but it simply get removed from the front position and doesn’t get back added to the stack of the cards.