Skip to content

Commit

Permalink
Move adapter manipulation logic to DpadDragHelper since it is error p…
Browse files Browse the repository at this point in the history
…rone
  • Loading branch information
rubensousa committed Jun 14, 2024
1 parent e1c677d commit a62e96d
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ package com.rubensousa.dpadrecyclerview
import android.view.KeyEvent
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import java.util.Collections

class DpadDragHelper(
/**
* A helper class for re-ordering the contents of a [DpadRecyclerView].
*
* To use this, your adapter needs to implement [DpadDragHelper.DragAdapter]
* and expose the mutable collection via [DpadDragHelper.DragAdapter.getMutableItems].
*/
class DpadDragHelper<T>(
private val adapter: DragAdapter<T>,
private val callback: DragCallback,
private val cancelKeyCodes: Set<Int> = setOf(
KeyEvent.KEYCODE_DPAD_CENTER,
Expand Down Expand Up @@ -48,6 +56,10 @@ class DpadDragHelper(
}
}

/**
* Attaches the [DpadRecyclerView] that will be dragged.
* This is required before calling [startDrag]
*/
fun attachRecyclerView(recyclerView: DpadRecyclerView) {
if (currentRecyclerView === recyclerView) {
return
Expand All @@ -56,11 +68,15 @@ class DpadDragHelper(
currentRecyclerView = recyclerView
}

/**
* Detaches the previously attached [DpadRecyclerView] and stops dragging
*/
fun detachFromRecyclerView() {
stopDrag()
currentRecyclerView = null
}


/**
* Starts the dragging action for the ViewHolder at [position].
*
Expand Down Expand Up @@ -125,24 +141,39 @@ class DpadDragHelper(
}
isDragging = false
callback.onDragStopped()
currentRecyclerView?.requestLayout()
}

private fun onKeyEvent(event: KeyEvent): Boolean {
val recyclerView = currentRecyclerView ?: return false
val direction = getFocusDirection(event) ?: return false
val view = recyclerView.focusSearch(direction) ?: return false
val viewHolder = recyclerView.findContainingViewHolder(view) ?: return false
val targetViewHolder = recyclerView.findContainingViewHolder(view) ?: return false
val selectedViewHolder = recyclerView.findViewHolderForAdapterPosition(
recyclerView.getSelectedPosition()
) ?: return false

if (selectedViewHolder.absoluteAdapterPosition == RecyclerView.NO_POSITION
|| viewHolder.absoluteAdapterPosition == RecyclerView.NO_POSITION
|| targetViewHolder.absoluteAdapterPosition == RecyclerView.NO_POSITION
) {
return false
}
return callback.move(src = selectedViewHolder, target = viewHolder)
val currentAdapter = recyclerView.adapter ?: return false
if (recyclerView.getSpanCount() == 1) {
moveLinear(
adapter = currentAdapter,
items = adapter.getMutableItems(),
srcIndex = selectedViewHolder.bindingAdapterPosition,
targetIndex = targetViewHolder.bindingAdapterPosition
)
} else {
moveGrid(
adapter = currentAdapter,
items = adapter.getMutableItems(),
srcIndex = selectedViewHolder.bindingAdapterPosition,
targetIndex = targetViewHolder.bindingAdapterPosition
)
}
return true
}

private fun getFocusDirection(event: KeyEvent): Int? {
Expand All @@ -155,28 +186,64 @@ class DpadDragHelper(
}
}

interface DragCallback {
/**
* A linear move just swaps positions
*/
private fun moveLinear(
adapter: RecyclerView.Adapter<*>,
items: MutableList<T>,
srcIndex: Int,
targetIndex: Int
) {
Collections.swap(items, srcIndex, targetIndex)
adapter.notifyItemMoved(srcIndex, targetIndex)
}

/**
* A grid move needs to remove the element at the given position
* and insert it in the new position, otherwise order is not kept
*/
private fun moveGrid(
adapter: RecyclerView.Adapter<*>,
items: MutableList<T>,
srcIndex: Int,
targetIndex: Int
) {
val item = items.removeAt(srcIndex)
items.add(targetIndex, item)
adapter.notifyItemMoved(srcIndex, targetIndex)
/**
* Now notify the range that was affected
* If src < target -> notify all indexes from src until target
* If src > target -> notify all indexes from target until src
*/
if (srcIndex < targetIndex) {
adapter.notifyItemRangeChanged(srcIndex, targetIndex - srcIndex)
} else {
adapter.notifyItemRangeChanged(targetIndex, srcIndex - targetIndex)
}
}

interface DragAdapter<T> {
/**
* @return true if [src] should be moved to [target]'s position,
* false to keep the original positions
* @return the mutable collection of items backing the adapter
*/
fun move(
src: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean
fun getMutableItems(): MutableList<T>
}

interface DragCallback {

/**
* Indicates that the dragging action has started. [DpadRecyclerView] will receive focus
*
* @param viewHolder the view holder that is being dragged
*/
fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {}
fun onDragStarted(viewHolder: RecyclerView.ViewHolder)

/**
* Indicates that the dragging action has stopped
*/
fun onDragStopped() {}
fun onDragStopped()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.rubensousa.dpadrecyclerview.sample.ui.screen.drag
import android.view.ViewGroup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.recyclerview.widget.RecyclerView
import com.rubensousa.dpadrecyclerview.DpadDragHelper
import com.rubensousa.dpadrecyclerview.compose.DpadComposeFocusViewHolder
import com.rubensousa.dpadrecyclerview.sample.ui.model.ListTypes
import com.rubensousa.dpadrecyclerview.sample.ui.widgets.common.MutableListAdapter
Expand All @@ -29,7 +30,10 @@ class DragAdapter(
private val dragState: StateFlow<Int?>,
private val onDragStart: (viewHolder: RecyclerView.ViewHolder) -> Unit,
private val gridLayout: Boolean = false
) : MutableListAdapter<Int, DpadComposeFocusViewHolder<Int>>(MutableGridAdapter.DIFF_CALLBACK) {
) : MutableListAdapter<Int, DpadComposeFocusViewHolder<Int>>(MutableGridAdapter.DIFF_CALLBACK),
DpadDragHelper.DragAdapter<Int> {

override fun getMutableItems(): MutableList<Int> = items

override fun onCreateViewHolder(
parent: ViewGroup,
Expand Down Expand Up @@ -64,8 +68,22 @@ class DragAdapter(
holder.itemView.contentDescription = item.toString()
}


override fun getItemViewType(position: Int): Int {
return ListTypes.ITEM
}

override fun toString(): String {
val builder = StringBuilder()
builder.append("[")
for (i in 0 until itemCount) {
builder.append(getItem(i))
if (i < itemCount - 1) {
builder.append(", ")
}
}
builder.append("]")
return builder.toString()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,17 @@ class DragAndDropFragment : Fragment(R.layout.screen_drag_drop) {
startDrag(viewHolder)
}
)
private val dragHelper = DpadDragHelper(object : DpadDragHelper.DragCallback {

override fun move(
src: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
dragAdapter.move(
from = src.bindingAdapterPosition,
to = target.bindingAdapterPosition
)
return true
}

override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition)
}

override fun onDragStopped() {
dragState.value = null
private val dragHelper = DpadDragHelper(
adapter = dragAdapter,
callback = object : DpadDragHelper.DragCallback {
override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition)
}
override fun onDragStopped() {
dragState.value = null
}
}

})
)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,23 @@ class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) {
},
gridLayout = true
)
private val dragHelper = DpadDragHelper(object : DpadDragHelper.DragCallback {

override fun move(
src: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
dragAdapter.move(
from = src.absoluteAdapterPosition,
to = target.absoluteAdapterPosition
)
return true
}

override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
dragState.value = dragAdapter.getItem(viewHolder.absoluteAdapterPosition)
}

override fun onDragStopped() {
dragState.value = null
private val dragHelper = DpadDragHelper(
adapter = dragAdapter,
callback = object : DpadDragHelper.DragCallback {
override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition)
}
override fun onDragStopped() {
dragState.value = null
}
}

})
)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setDragButtonContent()
dragAdapter.submitList(List(20) { it }.toMutableList())
dragAdapter.submitList(List(15) { it }.toMutableList())
}

private fun setupRecyclerView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ abstract class MutableListAdapter<T, VH : RecyclerView.ViewHolder>(
private val MAIN_THREAD_HANDLER = Handler(Looper.getMainLooper())
}

private var items: MutableList<T> = Collections.emptyList()
protected var items: MutableList<T> = Collections.emptyList()
private var currentVersion = 0

@SuppressLint("NotifyDataSetChanged")
Expand Down Expand Up @@ -128,12 +128,6 @@ abstract class MutableListAdapter<T, VH : RecyclerView.ViewHolder>(

}

fun move(from: Int, to: Int) {
currentVersion++
Collections.swap(items, from, to)
notifyItemMoved(from, to)
}

fun addAt(index: Int, item: T) {
currentVersion++
items.add(index, item)
Expand Down

0 comments on commit a62e96d

Please sign in to comment.