I've been trying to implement a very simple drag and drop in Android, where the dragged item swaps positions with the item it was dropped on.
I've successfully implemented the onLongClick and onDrag listeners. When I drag and drop an item it replaces the item it was dropped on, but I can't figure out how make the replaced item take the place of the dragged item.
I spent a while searching for answers, but most answers just linked complex implementations of drag and drop involving many files with lots of code, and I wasn't able to understand them enough to apply them to my code.
I originally assumed that I wasn't getting a true copy of the ImageView with
ImageView temp = (ImageView) event.getLocalState();
so I tried
ImageView dragged = (ImageView) findViewById(temp.getId());
but that didn't make a difference.
I'm almost certain the problem is in the 'OnDragListener', in the 'case DragEvent.ACTION_DROP:'. 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 the setImageDrawable works on target, but not on dragged, I'll be very grateful.
One of the great joys of being in Computer Science is learning humility. At least 90% of the time, the answer to the quest: "Why isn't this working?" is that you told the computer to do the wrong thing.
In "case DragEvent.ACTION_DROP:" I was getting references to both views. What I wasn't realizing for some reason is that when I did
target.setImageDrawable(dragged.getDrawable());
I was also changing the drawable of temp, since temp was referring to the actual view. Because of that when I did
dragged.setImageDrawable(temp.getDrawable());
I was actually just setting dragged's drawable 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;
I updated answer to actually swaps 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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With