How to swap two images using drag and drop on Android?
I am trying to implement a very simple drag and drop in Android, where the dragged item changes positions with the item it was dropped to.
I have successfully implemented the onLongClick and onDrag listeners. When I drag an item, it replaces the item it was deleted from, but I can't figure out how to replace the replaced item with the dragged item.
I spent some time looking for answers, but most of the answers just linked complex drag and drop implementations involving many files with a lot of code, and I couldn't figure them out enough to apply them to my code.
It was originally assumed that I did not get a genuine copy of the ImageView with
ImageView temp = (ImageView) event.getLocalState();
so i tried
ImageView dragged = (ImageView) findViewById(temp.getId());
but that didn't make any difference.
I'm pretty sure the problem is with the "OnDragListener", in the "DragEvent.ACTION_DROP:" case. Here is my code (from Main.java):
public class Main extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.ball1).setOnLongClickListener(listenClick);
findViewById(R.id.ball1).setOnDragListener(listenDrag);
findViewById(R.id.target1).setOnLongClickListener(listenClick);
findViewById(R.id.target1).setOnDragListener(listenDrag);
findViewById(R.id.target2).setOnDragListener(listenDrag);
findViewById(R.id.target2).setOnLongClickListener(listenClick);
findViewById(R.id.target3).setOnDragListener(listenDrag);
findViewById(R.id.target3).setOnLongClickListener(listenClick);
View.OnLongClickListener listenClick = new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
ClipData data = ClipData.newPlainText("","");
DragShadow dragShadow = new DragShadow(v);
v.startDrag(data, dragShadow, v, 0);
return false;
}
};
private class DragShadow extends View.DragShadowBuilder
{
ColorDrawable greyBox;
public DragShadow(View view)
{
super(view);
greyBox = new ColorDrawable(Color.LTGRAY);
}
@Override
public void onDrawShadow(Canvas canvas)
{
greyBox.draw(canvas);
}
@Override
public void onProvideShadowMetrics(Point shadowSize,
Point shadowTouchPoint)
{
View v = getView();
int height = (int) v.getHeight();
int width = (int) v.getWidth();
greyBox.setBounds(0, 0, width, height);
shadowSize.set(width, height);
shadowTouchPoint.set((int)width/2, (int)height/2);
}
}
View.OnDragListener listenDrag = new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event)
{
int dragEvent = event.getAction();
switch (dragEvent)
{
case DragEvent.ACTION_DRAG_ENTERED:
Log.i("Drag Event", "Entered");
break;
case DragEvent.ACTION_DRAG_EXITED:
Log.i("Drag Event", "Exited");
break;
case DragEvent.ACTION_DROP:
ImageView target = (ImageView) v;
ImageView temp = (ImageView) event.getLocalState();
ImageView dragged = (ImageView) findViewById(temp.getId());
target.setId(dragged.getId());
target.setImageDrawable(dragged.getDrawable());
dragged.setId(temp.getId());
dragged.setImageDrawable(temp.getDrawable());
break;
}
return true;
}
};
If anyone can tell me why setImageDrawable works on target but not on tasks, I would be very grateful.
source to share
One of the greatest joys in computer science is the study of humility. At least 90% of the time, the answer to the quest is: "Why doesn't it work?" is that you told the computer to do the wrong thing.
In the "case of DragEvent.ACTION_DROP:" I was getting links to both views. For some reason I didn't understand that when I was doing
target.setImageDrawable(dragged.getDrawable());
I also changed the returned temp as temp was referencing the actual view. Because of this, when I did
dragged.setImageDrawable(temp.getDrawable());
I was actually just setting up the drag and drop to what it originally was. Here is the corrected code.
case DragEvent.ACTION_DROP:
ImageView target = (ImageView) v;
ImageView dragged = (ImageView) event.getLocalState();
Drawable target_draw = target.getDrawable();
Drawable dragged_draw = dragged.getDrawable();
dragged.setImageDrawable(target_draw);
target.setImageDrawable(dragged_draw);
break;
source to share
I updated the answer to actually swap views instead of swapping images;
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
private fun initView(view: View) {
view.setOnDragListener(View.OnDragListener { target, event ->
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> return@OnDragListener true
DragEvent.ACTION_DRAG_ENTERED -> {
target.setBackgroundColor(ContextCompat.getColor(this, R.color.colorAccent))
return@OnDragListener true
}
DragEvent.ACTION_DRAG_LOCATION -> return@OnDragListener true
DragEvent.ACTION_DRAG_EXITED -> {
target.setBackgroundColor(ContextCompat.getColor(this, R.color.colorPrimary))
return@OnDragListener true
}
DragEvent.ACTION_DROP -> {
target.setBackgroundColor(ContextCompat.getColor(this, R.color.colorPrimary))
val dragged = event.localState as ImageView
val targetView = target as ImageView
//Swap Image
// val target_draw = targetView!!.drawable
// val dragged_draw = dragged!!.drawable
// dragged.setImageDrawable(target_draw)
// targetView.setImageDrawable(dragged_draw)
val oldOwner = dragged.parent as ViewGroup
val newOwner = target.parent as ViewGroup
val draggedPosition = oldOwner.indexOfChild(dragged)
val targetPosition = oldOwner.indexOfChild(dragged)
oldOwner.removeView(dragged)
newOwner.addView(dragged,targetPosition)
newOwner.removeView(target)
oldOwner.addView(target,draggedPosition)
return@OnDragListener true
}
DragEvent.ACTION_DRAG_ENDED -> return@OnDragListener true
else -> {
}
}
false
})
view.setOnLongClickListener { v ->
val dummyData = ClipData.newPlainText("dummyData", "") // don't forget to pass empty String
val shadowBuilder = View.DragShadowBuilder(v)
v.startDrag(dummyData, shadowBuilder, v, 0)
//val data = ClipData.newPlainText("value", "1")
// view.startDrag(data, View.DragShadowBuilder(v), null, 0)
true
}
}
source to share