Skip to content

Commit

Permalink
Merge pull request #264 from rubensousa/dragging_updates
Browse files Browse the repository at this point in the history
Expose fromUser in drag stop callback
  • Loading branch information
rubensousa authored Sep 4, 2024
2 parents a36875d + 55765a8 commit bf54c35
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 33 deletions.
2 changes: 1 addition & 1 deletion dpadrecyclerview/api/dpadrecyclerview.api
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public abstract interface class com/rubensousa/dpadrecyclerview/DpadDragHelper$D

public abstract interface class com/rubensousa/dpadrecyclerview/DpadDragHelper$DragCallback {
public abstract fun onDragStarted (Landroidx/recyclerview/widget/RecyclerView$ViewHolder;)V
public abstract fun onDragStopped ()V
public abstract fun onDragStopped (Z)V
}

public final class com/rubensousa/dpadrecyclerview/DpadLoopDirection : java/lang/Enum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class DragHelperGridTest {
private val numberOfItems = 50
private val spanCount = 5
private val dragStarted = mutableListOf<RecyclerView.ViewHolder>()
private var dragStopCount = 0
private val dragStopRequests = mutableListOf<DragStopRequest>()

@Before
fun setup() {
Expand Down Expand Up @@ -78,8 +78,8 @@ class DragHelperGridTest {
dragStarted.add(viewHolder)
}

override fun onDragStopped() {
dragStopCount++
override fun onDragStopped(fromUser: Boolean) {
dragStopRequests.add(DragStopRequest(fromUser))
}
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class DragHelperLinearTest {
private lateinit var testAdapter: TestAdapter
private val numberOfItems = 10
private val dragStarted = mutableListOf<RecyclerView.ViewHolder>()
private var dragStopCount = 0
private val dragStopRequests = mutableListOf<DragStopRequest>()

@Before
fun setup() {
Expand Down Expand Up @@ -80,8 +80,8 @@ class DragHelperLinearTest {
dragStarted.add(viewHolder)
}

override fun onDragStopped() {
dragStopCount++
override fun onDragStopped(fromUser: Boolean) {
dragStopRequests.add(DragStopRequest(fromUser))
}
}
)
Expand All @@ -100,7 +100,8 @@ class DragHelperLinearTest {
}

// then
assertThat(dragStopCount).isEqualTo(1)
assertThat(dragStopRequests).hasSize(1)
assertThat(dragStopRequests.first().fromUser).isFalse()
}

@Test
Expand Down Expand Up @@ -131,12 +132,11 @@ class DragHelperLinearTest {
}

@Test
fun testDragIsCanceledOnCertainKeyEvents() {
fun testDragStopsOnCertainKeyEvents() {
// given
val cancelKeyCodes = setOf(
KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_BACK,
)

// when
Expand All @@ -147,7 +147,10 @@ class DragHelperLinearTest {
}

// then
assertThat(dragStopCount).isEqualTo(cancelKeyCodes.size)
assertThat(dragStopRequests).hasSize(cancelKeyCodes.size)
dragStopRequests.forEach {
assertThat(it.fromUser).isTrue()
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.rubensousa.dpadrecyclerview.test.tests.drag

data class DragStopRequest(val fromUser: Boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ import java.util.Collections
*
* To use this, your adapter needs to implement [DpadDragHelper.DragAdapter]
* and expose the mutable collection via [DpadDragHelper.DragAdapter.getMutableItems].
*
* @param adapter the adapter that backs the data that can be arranged
* @param callback the callback for notifying dragging state changes
* @param stopKeyCodes key codes that will be accepted to stop the dragging state
*/
class DpadDragHelper<T>(
private val adapter: DragAdapter<T>,
private val callback: DragCallback,
private val cancelKeyCodes: Set<Int> = setOf(
private val stopKeyCodes: Set<Int> = setOf(
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_BACK
)
),
) {

/**
Expand All @@ -50,8 +53,11 @@ class DpadDragHelper<T>(
if (!isDragging) {
return false
}
if (cancelKeyCodes.contains(event.keyCode)) {
stopDrag()
if (stopKeyCodes.contains(event.keyCode)) {
if (event.action == KeyEvent.ACTION_UP) {
// Only stop dragging after the user releases the key
stopDragging(fromUser = true)
}
return true
}
if (event.action == KeyEvent.ACTION_UP) {
Expand Down Expand Up @@ -120,7 +126,7 @@ class DpadDragHelper<T>(

private fun startDrag(
recyclerView: DpadRecyclerView,
viewHolder: RecyclerView.ViewHolder
viewHolder: RecyclerView.ViewHolder,
) {
isDragging = true
previousKeyInterceptListener = recyclerView.getOnKeyInterceptListener()
Expand All @@ -136,6 +142,10 @@ class DpadDragHelper<T>(
* [DragCallback.onDragStopped] will be called after this method
*/
fun stopDrag() {
stopDragging(fromUser = false)
}

private fun stopDragging(fromUser: Boolean) {
if (!isDragging) {
return
}
Expand All @@ -145,7 +155,7 @@ class DpadDragHelper<T>(
}
}
isDragging = false
callback.onDragStopped()
callback.onDragStopped(fromUser)
}

private fun onKeyEvent(event: KeyEvent): Boolean {
Expand Down Expand Up @@ -198,7 +208,7 @@ class DpadDragHelper<T>(
adapter: RecyclerView.Adapter<*>,
items: MutableList<T>,
srcIndex: Int,
targetIndex: Int
targetIndex: Int,
) {
Collections.swap(items, srcIndex, targetIndex)
adapter.notifyItemMoved(srcIndex, targetIndex)
Expand All @@ -212,7 +222,7 @@ class DpadDragHelper<T>(
adapter: RecyclerView.Adapter<*>,
items: MutableList<T>,
srcIndex: Int,
targetIndex: Int
targetIndex: Int,
) {
val item = items.removeAt(srcIndex)
items.add(targetIndex, item)
Expand Down Expand Up @@ -247,8 +257,10 @@ class DpadDragHelper<T>(

/**
* Indicates that the dragging action has stopped
* @param fromUser true if this was triggered by the user,
* or false if [stopDrag] was called manually by the app
*/
fun onDragStopped()
fun onDragStopped(fromUser: Boolean)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ package com.rubensousa.dpadrecyclerview.sample.ui.screen.drag

import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.RecyclerView
import com.rubensousa.dpadrecyclerview.DpadDragHelper
Expand All @@ -32,6 +36,7 @@ import com.rubensousa.dpadrecyclerview.sample.databinding.ScreenDragDropBinding
import com.rubensousa.dpadrecyclerview.sample.ui.dpToPx
import com.rubensousa.dpadrecyclerview.sample.ui.viewBinding
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

class DragAndDropFragment : Fragment(R.layout.screen_drag_drop) {

Expand All @@ -49,17 +54,36 @@ class DragAndDropFragment : Fragment(R.layout.screen_drag_drop) {
override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition)
}
override fun onDragStopped() {

override fun onDragStopped(fromUser: Boolean) {
if (fromUser) {
// User requested dragging to stop
} else {
// Dragging was stopped manually
}
dragState.value = null
}
}
)
private val backPressCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
dragHelper.stopDrag()
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setDragButtonContent()
dragAdapter.submitList(List(20) { it }.toMutableList())
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressCallback)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
dragState.collect { draggingItem ->
backPressCallback.isEnabled = draggingItem != null
}
}
}
}

private fun setupRecyclerView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import com.rubensousa.dpadrecyclerview.sample.R
import com.rubensousa.dpadrecyclerview.sample.databinding.ScreenDragDropBinding
import com.rubensousa.dpadrecyclerview.sample.ui.dpToPx
import com.rubensousa.dpadrecyclerview.sample.ui.viewBinding
import com.rubensousa.dpadrecyclerview.spacing.DpadGridSpacingDecoration
import kotlinx.coroutines.flow.MutableStateFlow

class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) {
Expand All @@ -51,7 +50,7 @@ class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) {
override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
dragState.value = dragAdapter.getItem(viewHolder.bindingAdapterPosition)
}
override fun onDragStopped() {
override fun onDragStopped(fromUser: Boolean) {
dragState.value = null
}
}
Expand All @@ -72,12 +71,8 @@ class DragAndDropGridFragment : Fragment(R.layout.screen_drag_drop_grid) {
// For faster moves
moveDuration = 100
}
addItemDecoration(
DpadGridSpacingDecoration.create(
itemSpacing = dpToPx(16.dp),
edgeSpacing = dpToPx(48.dp),
)
)
setItemSpacing(dpToPx(16.dp))
setItemEdgeSpacing(dpToPx(48.dp))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fun DraggableItem(
}
Box(
modifier = modifier
.size(200.dp)
.size(100.dp)
.then(
if (isDragging) {
Modifier.border(8.dp, Color.Blue, RoundedCornerShape(8.dp))
Expand Down
5 changes: 3 additions & 2 deletions sample/src/main/res/layout/horizontal_adapter_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
android:clipChildren="false"
android:gravity="center"
android:orientation="horizontal"
app:dpadRecyclerViewChildAlignmentFraction="0.5"
app:dpadRecyclerViewChildAlignmentFraction="0"
app:dpadRecyclerViewFocusOutBack="false"
app:dpadRecyclerViewFocusOutFront="false"
app:dpadRecyclerViewItemEdgeSpacing="@dimen/list_margin_start"
app:dpadRecyclerViewItemSpacing="@dimen/item_spacing"
app:dpadRecyclerViewParentAlignmentEdge="min_max"
app:dpadRecyclerViewParentAlignmentFraction="0.5" />
app:dpadRecyclerViewParentAlignmentFraction="0"
app:dpadRecyclerViewParentAlignmentOffset="@dimen/list_margin_start" />


</LinearLayout>

0 comments on commit bf54c35

Please sign in to comment.