mirror of
https://github.com/AdrianKuta/Unbound-Drag-Drop.git
synced 2025-04-19 22:49:02 +02:00
Cleanup
This commit is contained in:
parent
5a41df0b16
commit
6f93d0c234
1
build-logic/convention/.gitignore
vendored
Normal file
1
build-logic/convention/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
1
dragdrop/.gitignore
vendored
Normal file
1
dragdrop/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
56
dragdrop/build.gradle.kts
Normal file
56
dragdrop/build.gradle.kts
Normal file
@ -0,0 +1,56 @@
|
||||
import com.vanniktech.maven.publish.SonatypeHost
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.convention.android.library)
|
||||
alias(libs.plugins.convention.android.library.publish)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "dev.adriankuta.unbounddragdrop"
|
||||
version = "0.0.2"
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
coordinates("dev.adriankuta", "unbound-drag-drop", version.toString())
|
||||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
|
||||
signAllPublications()
|
||||
pom {
|
||||
name = "Unbound Drag & Drop"
|
||||
description =
|
||||
"Unbound Drag & Drop enhances your Android apps by enabling drag and drop across multiple RecyclerViews, unlike the default single RecyclerView restriction. This feature allows users to seamlessly move items between different RecyclerViews, offering a more flexible and intuitive user experience."
|
||||
inceptionYear = "2024"
|
||||
url = "https://github.com/AdrianKuta/Unbound-Drag-Drop"
|
||||
licenses {
|
||||
license {
|
||||
name.set("MIT License")
|
||||
url.set("https://github.com/AdrianKuta/Unbound-Drag-Drop/blob/master/LICENSE")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "AdrianKuta"
|
||||
name = "Adrian Kuta"
|
||||
url = "https://adriankuta.dev/"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/AdrianKuta/Unbound-Drag-Drop"
|
||||
connection = "scm:git:git://github.com/AdrianKuta/Unbound-Drag-Drop.git"
|
||||
developerConnection = "scm:git:ssh://git@github.com/AdrianKuta/Unbound-Drag-Drop.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package dev.adriankuta.unbounddragdrop
|
||||
|
||||
import android.content.ClipData
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.DRAG_FLAG_OPAQUE
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
/**
|
||||
* Helper class to handle drag and drop functionality in a RecyclerView.
|
||||
*
|
||||
* @param callback A Callback object to handle the drag and drop events.
|
||||
*/
|
||||
class DragDropHelper(callback: Callback) :
|
||||
RecyclerView.OnChildAttachStateChangeListener {
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private var recyclerItemClickListener: RecyclerItemClickListener? = null
|
||||
private val dropListener = DropListener(callback)
|
||||
private val onItemLongClickListener: RecyclerItemClickListener.OnItemLongClickListener by lazy {
|
||||
object : RecyclerItemClickListener.OnItemLongClickListener {
|
||||
/**
|
||||
* Called when an item is long-clicked. Starts the drag operation.
|
||||
*
|
||||
* @param view The view that was long-clicked.
|
||||
* @param position The position of the item in the adapter.
|
||||
*/
|
||||
override fun onItemLongClick(view: View, position: Int) {
|
||||
val data = ClipData.newPlainText("", "")
|
||||
val shadowBuilder = View.DragShadowBuilder(view)
|
||||
view.startDragAndDrop(data, shadowBuilder, view, DRAG_FLAG_OPAQUE)
|
||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the DragDropHelper to the specified RecyclerView.
|
||||
*
|
||||
* @param recyclerView The RecyclerView to attach to.
|
||||
*/
|
||||
fun attachToRecyclerView(recyclerView: RecyclerView?) {
|
||||
if (mRecyclerView === recyclerView) {
|
||||
return // nothing to do
|
||||
}
|
||||
if (mRecyclerView != null) {
|
||||
destroyCallbacks()
|
||||
}
|
||||
mRecyclerView = recyclerView
|
||||
mRecyclerView?.let {
|
||||
setupCallbacks()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the necessary callbacks for the RecyclerView.
|
||||
*/
|
||||
private fun setupCallbacks() {
|
||||
mRecyclerView?.apply {
|
||||
recyclerItemClickListener = RecyclerItemClickListener(
|
||||
context,
|
||||
this,
|
||||
onItemLongClickListener
|
||||
).also {
|
||||
mRecyclerView?.addOnItemTouchListener(it)
|
||||
}
|
||||
addOnChildAttachStateChangeListener(this@DragDropHelper)
|
||||
setOnDragListener(dropListener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the callbacks from the RecyclerView.
|
||||
*/
|
||||
private fun destroyCallbacks() {
|
||||
recyclerItemClickListener?.let {
|
||||
mRecyclerView?.removeOnItemTouchListener(it)
|
||||
}
|
||||
mRecyclerView?.removeOnChildAttachStateChangeListener(this)
|
||||
mRecyclerView?.setOnDragListener(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child view is attached to the RecyclerView.
|
||||
*
|
||||
* @param view The child view that was attached.
|
||||
*/
|
||||
override fun onChildViewAttachedToWindow(view: View) {
|
||||
view.setOnDragListener(dropListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child view is detached from the RecyclerView.
|
||||
*
|
||||
* @param view The child view that was detached.
|
||||
*/
|
||||
override fun onChildViewDetachedFromWindow(view: View) {
|
||||
view.setOnDragListener(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class to handle drag and drop events.
|
||||
*/
|
||||
abstract class Callback {
|
||||
|
||||
/**
|
||||
* Called when an item is moved within or between RecyclerViews.
|
||||
*
|
||||
* @param recyclerView The RecyclerView containing the dragged item.
|
||||
* @param viewHolder The ViewHolder of the dragged item.
|
||||
* @param targetRecyclerView The RecyclerView where the item is dropped.
|
||||
* @param targetViewHolder The ViewHolder of the target position.
|
||||
* @return True if the move was handled, false otherwise.
|
||||
*/
|
||||
abstract fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: ViewHolder,
|
||||
targetRecyclerView: RecyclerView,
|
||||
targetViewHolder: ViewHolder?
|
||||
): Boolean
|
||||
|
||||
/**
|
||||
* Called when an item has been dropped.
|
||||
*
|
||||
* @param recyclerView The RecyclerView containing the dragged item.
|
||||
* @param viewHolder The ViewHolder of the dragged item.
|
||||
* @param targetRecyclerView The RecyclerView where the item is dropped.
|
||||
* @param targetViewHolder The ViewHolder of the target position.
|
||||
*/
|
||||
abstract fun onMoved(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: ViewHolder,
|
||||
targetRecyclerView: RecyclerView,
|
||||
targetViewHolder: ViewHolder?
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package dev.adriankuta.unbounddragdrop
|
||||
|
||||
import android.view.DragEvent
|
||||
import android.view.View
|
||||
import android.view.View.OnDragListener
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
internal class DropListener(private val callback: DragDropHelper.Callback) : OnDragListener {
|
||||
|
||||
/**
|
||||
* Handles drag events on the target view.
|
||||
*
|
||||
* @param targetView The view that is being dragged over or dropped onto.
|
||||
* @param event The drag event.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
*/
|
||||
override fun onDrag(targetView: View?, event: DragEvent?): Boolean {
|
||||
targetView ?: return false
|
||||
event?.let {
|
||||
if (it.action == DragEvent.ACTION_DROP) {
|
||||
val sourceView = it.localState as View
|
||||
val sourceRecyclerView = sourceView.parent as RecyclerView
|
||||
val sourcePosition = sourceRecyclerView.getChildAdapterPosition(sourceView)
|
||||
val sourceViewHolder =
|
||||
sourceRecyclerView.findViewHolderForAdapterPosition(sourcePosition)
|
||||
?: return false
|
||||
|
||||
val targetRecyclerView = getRecyclerView(targetView) ?: return false
|
||||
val targetAdapter = targetRecyclerView.adapter
|
||||
val targetPosition = if (targetView is RecyclerView) {
|
||||
targetAdapter?.itemCount ?: 0
|
||||
} else {
|
||||
targetRecyclerView.getChildAdapterPosition(targetView)
|
||||
}
|
||||
val targetViewHolder =
|
||||
targetRecyclerView.findViewHolderForAdapterPosition(targetPosition)
|
||||
|
||||
if (callback.onMove(
|
||||
sourceRecyclerView,
|
||||
sourceViewHolder,
|
||||
targetRecyclerView,
|
||||
targetViewHolder
|
||||
)
|
||||
) {
|
||||
callback.onMoved(
|
||||
sourceRecyclerView,
|
||||
sourceViewHolder,
|
||||
targetRecyclerView,
|
||||
targetViewHolder
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the RecyclerView associated with the given view.
|
||||
*
|
||||
* @param view The view to check.
|
||||
* @return The RecyclerView if found, null otherwise.
|
||||
*/
|
||||
private fun getRecyclerView(view: View): RecyclerView? {
|
||||
return view as? RecyclerView ?: view.parent as? RecyclerView
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package dev.adriankuta.unbounddragdrop
|
||||
|
||||
import android.content.Context
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
internal class RecyclerItemClickListener(
|
||||
context: Context,
|
||||
private val recyclerView: RecyclerView,
|
||||
private val listener: OnItemLongClickListener?
|
||||
) : RecyclerView.OnItemTouchListener {
|
||||
|
||||
private val gestureDetector: GestureDetector =
|
||||
GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||
/**
|
||||
* Called when a long press gesture is detected.
|
||||
*
|
||||
* @param e The motion event triggering the long press.
|
||||
*/
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
val childView = recyclerView.findChildViewUnder(e.x, e.y)
|
||||
if (childView != null && listener != null) {
|
||||
listener.onItemLongClick(
|
||||
childView,
|
||||
recyclerView.getChildAdapterPosition(childView)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a single tap up gesture is detected.
|
||||
*
|
||||
* @param e The motion event triggering the single tap.
|
||||
* @return True to indicate the event is handled.
|
||||
*/
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when an item in this
|
||||
* RecyclerView has been long clicked.
|
||||
*/
|
||||
interface OnItemLongClickListener {
|
||||
/**
|
||||
* Called when an item has been long clicked.
|
||||
*
|
||||
* @param view The view that was clicked.
|
||||
* @param position The position of the view in the adapter.
|
||||
*/
|
||||
fun onItemLongClick(view: View, position: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept touch events to determine if a long press has occurred.
|
||||
*
|
||||
* @param rv The RecyclerView.
|
||||
* @param e The motion event.
|
||||
* @return True if the event is intercepted, false otherwise.
|
||||
*/
|
||||
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
|
||||
val childView = rv.findChildViewUnder(e.x, e.y)
|
||||
return childView != null && childView.isLongClickable && gestureDetector.onTouchEvent(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle touch events (not needed for this implementation).
|
||||
*
|
||||
* @param rv The RecyclerView.
|
||||
* @param e The motion event.
|
||||
*/
|
||||
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
|
||||
// Not needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to disallow intercepting touch events (not needed for this implementation).
|
||||
*
|
||||
* @param disallowIntercept True to disallow intercepting touch events.
|
||||
*/
|
||||
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
||||
// Not needed
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user