DragEvent Examples - Drap Drop
In this tutorial you will learn about a DragEvent
using several Drag and Drop step by examples.
What is DragEvent?
The system transmits a DragEvent
when a drag and drop operation is performed. This dragEvent is a data structure that contains several pieces of information about the operation and the data that is being moved.
An object receiving a drag event calls the getAction()
method, which then returns the state of the drag and drop operation. By doing this, a View
object can change its appearance or perform other actions when its state changes. For example, a View
can react to the ACTION_DRAG_ENTERED
action type by by changing one or more colors in its displayed image.
This image is called a drag shadow. Several action types reflect the position of the drag shadow relative to the View receiving the event when a user drags and drops an image.
Examples
Let's assume you are creating some cool notes app and you want to give users the ability to drag and drop the individual notes item and save their final positions let's say in a database. Well the first step in that is understanding how to drag drop items in an adapterview, e.g listview, gridview, recyclerview.
In this example you will learn:
- How to drag and drop items in a grid.
- How to get the items final position.
We use the following languages:
- Kotlin
- Java
NB/= This article is broken down into several pages. Each page teaches a unique concept. Here are the taught concepts:
- Drag Drop in a Grid
- Drag Drop in a RecyclerView
Example 1: Drap Drop Items in a GridView
Let us see how to drag drop items in a GridView. This is a simple example.
Step 1: Dependencies
We use no third party dependencies.
Step 2: Code
We write our code in Java.
We have two classes, a fragment hosted inside an activity:
MainFragment.java
package info.camposha.dragdropexample;
import android.content.ClipData;
import android.graphics.Color;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
public class MainFragment extends Fragment {
private View[] views;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_main, container, false);
views = new View[rootView.getChildCount()];
for (int i = 0; i < rootView.getChildCount(); i++) {
final TextView view = (TextView) rootView.getChildAt(i);
initView(view);
views[i] = view;
}
return rootView;
}
private void initView(final TextView view) {
view.setOnDragListener((v, event) -> {
switch(event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return true;
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackgroundColor(Color.LTGRAY);
return true;
case DragEvent.ACTION_DRAG_LOCATION:
return true;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackgroundColor(Color.TRANSPARENT);
return true;
case DragEvent.ACTION_DROP:
v.setBackgroundColor(Color.TRANSPARENT);
int dragVal = Integer.parseInt(event.getClipData().getItemAt(0).getText().toString());
int viewVal = Integer.parseInt(((TextView) v).getText().toString());
((TextView) v).setText("" + (dragVal + viewVal));
Toast.makeText(getContext(), "Added", Toast.LENGTH_SHORT).show();
return true;
case DragEvent.ACTION_DRAG_ENDED:
return true;
default:
break;
}
return false;
});
view.setOnLongClickListener(v -> {
ClipData data = ClipData.newPlainText("value", view.getText());
view.startDrag(data, new View.DragShadowBuilder(v), null, 0);
return true;
});
}
}
MainActivity
To host our fragment:
package info.camposha.dragdropexample;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Step 3: Layouts
Copy the layout from the source code.
Step 4: Run
If your run the project you'll get the following:
Step 5: Download
Download the full source code here.
Example 2: Kotlin Android Drag Drop
This is a simple example showing howto implement drag and drop in Kotlin.
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Dependencies
No third party library is needed for our implementation of Drag Drop.
Step 3: Create a Drag and Drop Container
You need to create a custom Drag and drop layout. This layout is based on the FrameLayout and will be used in our layout:
DragAndDropContainer.kt
package supahsoftware.androidexampledragdrop
import android.content.ClipData
import android.content.ClipDescription
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
class DragAndDropContainer(
context: Context,
attrs: AttributeSet?
) : FrameLayout(context, attrs) {
private val dragAndDropListener by lazy { DragAndDropListener() }
private var content: View? = null
init {
setOnDragListener(dragAndDropListener)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
updateChild()
}
fun setContent(view: View) {
removeAllViews()
addView(view)
updateChild()
}
fun removeContent(view: View) {
view.setOnLongClickListener(null)
removeView(view)
updateChild()
}
private fun updateChild() {
validateChildCount()
content = getFirstChild()
content?.setOnLongClickListener { startDrag() }
}
private fun getFirstChild() = if (childCount == 1) getChildAt(0) else null
private fun validateChildCount() = check(childCount <= 1) {
"There should be a maximum of 1 child inside of a DragAndDropContainer, but there were $childCount"
}
private fun startDrag(): Boolean {
content?.let {
val tag = it.tag as? CharSequence
val item = ClipData.Item(tag)
val data = ClipData(tag, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item)
val shadow = DragShadowBuilder(it)
if (Build.VERSION.SDK_INT >= 24) {
it.startDragAndDrop(data, shadow, it, 0)
} else {
it.startDrag(data, shadow, it, 0)
}
return true
} ?: return false
}
}
Step 4: Design Layout
Design your MainActivity's layout as follows:
activity_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">
<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_1"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/content_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/banner"
android:scaleType="center" />
</FrameLayout>
</supahsoftware.androidexampledragdrop.DragAndDropContainer>
<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_2"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toBottomOf="@id/container_1" />
<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_3"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toBottomOf="@id/container_2" />
<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_4"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toBottomOf="@id/container_3" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 5: Create a Drag and Drop Listener
As follows:
DragAndDropListener.kt
package supahsoftware.androidexampledragdrop
import android.content.ClipDescription
import android.view.DragEvent
import android.view.View
import androidx.core.content.ContextCompat
class DragAndDropListener : View.OnDragListener {
override fun onDrag(view: View, event: DragEvent): Boolean {
return when (event.action) {
DragEvent.ACTION_DRAG_ENTERED -> {
view.setDashedOutline(); true
}
DragEvent.ACTION_DRAG_EXITED -> {
view.setSolidOutline(); true
}
DragEvent.ACTION_DROP -> {
val draggingView = event.localState as View
val draggingViewParent = draggingView.parent as DragAndDropContainer
draggingViewParent.removeContent(draggingView)
val landingContainer = view as DragAndDropContainer
landingContainer.setContent(draggingView)
landingContainer.setSolidOutline()
true
}
DragEvent.ACTION_DRAG_STARTED -> event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
else -> true
}
}
private fun View.setSolidOutline() {
background = ContextCompat.getDrawable(context, R.drawable.outline_gray_solid)
}
private fun View.setDashedOutline() {
background = ContextCompat.getDrawable(context, R.drawable.outline_gray_dashed)
}
}
Step 6: Create MainActivity
Create MainActivity as follows:
MainActivity.kt
package supahsoftware.androidexampledragdrop
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
3. | Code: Apache 2.0 License |
Example 1: Implement Drag and Drop in Recyclerview
In this section we will look at Drag and Drop implementation in a RecyclerView.
Here we see a Words and sentence app that teaches implementation of Drag and drop in a recyclerview.
Here is the demo of the project:
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Dependencies
Add dependencies as follows:
dependencies {
kotlin("stdlib")
implementation("com.google.android.material:material:1.4.0")
implementation("androidx.appcompat:appcompat:1.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.0")
implementation("com.google.android:flexbox:2.0.1")
}
Step 3: Design Layouts
Design two layouts as follows:
(a). item_word.xml
Layout for a single word:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tvWord"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="@drawable/bg_word"
android:elevation="4dp"
android:padding="12dp"
android:textColor="@android:color/black"
tools:text="Drag and Drop" />
(b). activity_main.xml
Will contain two recyclerviews. Item will be dragged from one recyclerview and dropped onto another:
<?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"
android:background="@drawable/bg_gradient"
tools:context=".MainActivity">
<androidx.cardview.widget.CardView
android:id="@+id/rvSentenceCard"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toTopOf="@id/rvWords"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSentence"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
</androidx.cardview.widget.CardView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvWords"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:overScrollMode="never"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvSentenceCard" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4: Create CallBacks
(a). DragListener.kt
A DragListener
callback for the draggable view to update it's visibility:
class DragListener : View.OnDragListener {
override fun onDrag(view: View, dragEvent: DragEvent): Boolean {
when (dragEvent.action) {
// hide view when drag has been started
DragEvent.ACTION_DRAG_ENTERED -> view.visibility = View.INVISIBLE
// show view when drag has been ended
DragEvent.ACTION_DRAG_ENDED -> view.visibility = View.VISIBLE
}
return true
}
}
(a). DropListener.kt
A DropListener
Callback for the target view where dragged items will be dropped
class DropListener(private val onDrop: () -> Unit) : View.OnDragListener {
override fun onDrag(view: View, dragEvent: DragEvent): Boolean {
when (dragEvent.action) {
// animate and highlight a background under the view to show the borders of the drop area
DragEvent.ACTION_DRAG_ENTERED -> {
val bgColor = ContextCompat.getColor(view.context, R.color.colorWhiteTransparent)
if (view.background is ColorDrawable && (view.background as ColorDrawable).color == bgColor) return true
ValueAnimator.ofArgb(Color.TRANSPARENT, bgColor).apply {
addUpdateListener {
val color = it.animatedValue as Int
view.setBackgroundColor(color)
}
duration = 500
}.start()
}
// animate and hide the highlight under the drop area
DragEvent.ACTION_DRAG_ENDED -> {
val bgColor = ContextCompat.getColor(view.context, R.color.colorWhiteTransparent)
if (view.background is ColorDrawable && (view.background as ColorDrawable).color == Color.TRANSPARENT) return true
ValueAnimator.ofArgb(bgColor, Color.TRANSPARENT).apply {
addUpdateListener {
val color = it.animatedValue as Int
view.setBackgroundColor(color)
}
duration = 500
}.start()
}
// when item has been dropped, notify about it
DragEvent.ACTION_DROP -> onDrop()
}
return true
}
}
(c). WordsDiffCallback.kt
This is a DiffUtil.ItemCallback
for our adapters:
class WordsDiffCallback : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean = oldItem == newItem
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean = oldItem == newItem
}
Step 5: Create Adapters
(a). WordsAdapter.kt
A androidx.recyclerview.widget.RecyclerView
adapter to show draggable items. The @param onDragStarted
will provide the current draggable view value, String in this case:
class WordsAdapter(private val onDragStarted: (String) -> Unit) : ListAdapter<String, WordsAdapter.WordsViewHolder>(WordsDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_word, parent, false)
return WordsViewHolder(view)
}
override fun onBindViewHolder(holder: WordsViewHolder, position: Int) {
holder.bind(getItem(position))
}
fun removeItem(selectedWord: String) {
val list = ArrayList(currentList)
list.remove(selectedWord)
submitList(list)
}
inner class WordsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(word: String) = itemView.run {
findViewById<TextView>(R.id.tvWord).text = word
setOnLongClickListener { view ->
// when user is long clicking on a view, drag process will start
val data = ClipData.newPlainText("", word)
val shadowBuilder = View.DragShadowBuilder(view)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
view.startDragAndDrop(data, shadowBuilder, view, 0)
} else {
view.startDrag(data, shadowBuilder, view, 0)
}
onDragStarted(word)
true
}
setOnDragListener(DragListener())
}
}
}
(b). SentenceAdapter.kt
A androidx.recyclerview.widget.RecyclerView
adapter to show dropped items:
class SentenceAdapter : ListAdapter<String, SentenceAdapter.WordsViewHolder>(WordsDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_word, parent, false)
return WordsViewHolder(view)
}
override fun onBindViewHolder(holder: WordsViewHolder, position: Int) {
holder.bind(getItem(position))
}
fun addItem(selectedWord: String) {
val list = ArrayList(currentList)
list.add(selectedWord)
submitList(list)
}
inner class WordsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(word: String) {
itemView.findViewById<TextView>(R.id.tvWord).text = word
}
}
}
Step 6: Create MainActivity
Lastly create mainactivity as follows:
MainActivity.kt
class MainActivity : AppCompatActivity() {
// values of the draggable views (usually this should come from a data source)
private val words = mutableListOf("world", "a", "!", "What", "wonderful")
// last selected word
private var selectedWord = ""
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val sentenceAdapter = SentenceAdapter()
val wordsAdapter = WordsAdapter {
selectedWord = it
}.apply {
submitList(words)
}
binding.rvSentence.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
binding.rvSentence.adapter = sentenceAdapter
binding.rvSentence.setOnDragListener(
DropListener {
wordsAdapter.removeItem(selectedWord)
sentenceAdapter.addItem(selectedWord)
}
)
binding.rvWords.layoutManager = FlexboxLayoutManager(this, FlexDirection.ROW, FlexWrap.WRAP).apply {
justifyContent = JustifyContent.SPACE_EVENLY
alignItems = AlignItems.CENTER
}
binding.rvWords.adapter = wordsAdapter
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
3. | Code: Apache 2.0 License |