I'm really having a time figuring this one out and can't find any friends with the relevant experience so far. It's my VERY LAST FEATURE before I release my VERY FIRST APP so it's driving me crazy to have gotten bogged down with it with the end in sight!
My drag and drop works perfectly if I drop the object in an acceptable area. However, if dropped somewhere else I do not get an event and cannot clean up after the failed drag drop operation. I need the ACTION_DRAG_ENDED event to fire. However this only fires (according to the documentation) if I respond to ACTION_DRAG_STARTED with a value of true (meaning it can accept the object). The problem is ACTION_DRAG_STARTED is not firing!
So I believe the crux of the matter is that I'm not getting ACTION_DRAG_STARTED. I'm putting what should be all of the relevant dragdrop code (or at least nearly all) here:
private OnTouchListener onTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Item current = (Item) ((View) v.getTag()).getTag();
//ClipData data = ClipData.newPlainText("test", "testText");
View dragView = (View) v.getTag();
DragShadowBuilder shadowBuilder = new ItemDragShadowBuilder(dragView,
new Point(v.getWidth()/2, v.getHeight()/2));
dragSource = dragView;
v.startDrag(null, shadowBuilder, current, 0);
dragView.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
};
private static class ItemDragShadowBuilder extends View.DragShadowBuilder {
private Point touchPoint = null;
private Point sizePoint = null;
private View dragView = null;
public ItemDragShadowBuilder(View dragView, Point touchPoint) {
super();
sizePoint = new Point(dragView.getWidth(), dragView.getHeight());
this.touchPoint = touchPoint;
this.dragView = dragView;
}
@Override
public void onProvideShadowMetrics (Point size, Point touch) {
size.set(sizePoint.x, sizePoint.y);
touch.set(touchPoint.x,touchPoint.y);
}
@Override
public void onDrawShadow(Canvas canvas) {
dragView.setBackgroundColor(dragView.getContext().getResources().getColor(R.color.holoBlueDark));
dragView.draw(canvas);
//canvas.setBitmap(dragView.getDrawingCache());
//canvas.drawColor(Color.WHITE);
//dragView.setAlpha(1);
//How do I change the transparency?
//super.onDrawShadow(canvas);
}
}
private View dragSource = null;
private OnDragListener onDragEventListener = new OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
final int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
Toast.makeText(v.getContext(), ((EditText) v.findViewById(R.id.edtParent)).getText().toString() + " started", Toast.LENGTH_SHORT/8).show();
return true;
//break;//DRAG AND DROP>>>START DOESN"T FIRE
case DragEvent.ACTION_DROP:
if ((dragSource != null) && (dragSource != v)) {
Item source = (Item) dragSource.getTag();
Item target = (Item) v.getTag();
int sourcePos = source.getPosition();
int targetPos = target.getPosition();
if (targetPos < sourcePos)
source.moveUp(sourcePos - targetPos);
else
source.moveDown(targetPos - sourcePos);
dragSource.setVisibility(View.VISIBLE);
dragSource = null;
fireOnChecklistNeedsResetListener();
return true;
}
// View view = (View) event.getLocalState();
// Toast.makeText(v.getContext(), ((EditText) v.findViewById(R.id.edtParent)).getText().toString(), Toast.LENGTH_SHORT/8).show();
// Toast.makeText(v.getContext(), ((EditText) view.findViewById(R.id.edtParent)).getText().toString(), Toast.LENGTH_SHORT/8).show();
// Dropped, reassign View to ViewGroup
// View view = (View) event.getLocalState();
// ViewGroup owner = (ViewGroup) view.getParent();
// owner.removeView(view);
// LinearLayout container = (LinearLayout) v;
// container.addView(view);
// view.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
dragSource.setVisibility(View.VISIBLE);
dragSource = null;
fireOnChecklistNeedsResetListener();
if (!event.getResult())
Toast.makeText(v.getContext(), "UNHANDLED", Toast.LENGTH_SHORT/8).show();
// View vieww = (View) event.getLocalState();
// Toast.makeText(v.getContext(), ((EditText) v.findViewById(R.id.edtParent)).getText().toString(), Toast.LENGTH_SHORT/8).show();
// Toast.makeText(v.getContext(), ((EditText) vieww.findViewById(R.id.edtParent)).getText().toString(), Toast.LENGTH_SHORT/8).show();
break;
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackgroundColor(v.getContext().getResources().getColor(R.color.holoBlueLight));
return true;
//break;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackgroundColor(v.getContext().getResources().getColor(android.R.color.background_light));
return false;
//break;
default:
break;
}
return false;
}
};
Extended information: The dragView is an item layout in a listview (specifically an expandablelistview). The view that accepts the touch event is a small glyph on the view with a small grip. Here is a screenshot with a drag operation in progress (took some fancy finger work to get this lol):
So if I were to drop the dragged item on another item (this reorders them) it works great. If I drop it somewhere else no event fires so I can never clean it up. So for example in the upper part of the screen or if the list is short in a blank area or the blue bar on bottom or any of the system areas.
I could alternatively solve this problem by figuring out a way to prevent dragging away from a valid area so if you know how to do that, that's another way to solve it. My last option would be to implement an event for every possible view on the screen (again these are list view layouts, on a listview, which is on a fragment, which could be on various activities) so obviously this would not be desirable. And in fact if I do that I still may have a problem if it's dropped in a system UI area (like systembar or capacitive buttons area) but I'm not sure.
In case it is helpful here is where I assign the relevant event procedures (I removed irrelevant lines) in the list view adapters get view item method:
public View getGroupEdit(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
if (convertView != null) {
//irrelevant stuff omitted
} else
convertView = inf.inflate(R.layout.edit_group_item, null);
Item group = (Item) getGroup(groupPosition);
convertView.setTag(group);
EditText edtParent = (EditText) convertView.findViewById(R.id.edtParent);
edtParent.setTag(convertView);
scatterItemText(edtParent);
//irrelevant stuff omitted
ImageView imgGrip = (ImageView) convertView.findViewById(R.id.imgGrip);
imgGrip.setTag(convertView);
//irrelevant stuff omitted
imgGrip.setOnTouchListener(onTouchListener);
convertView.setOnDragListener(onDragEventListener);
return convertView;
}
And finally FWIW, this is the layout XML for the list item view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="55dip"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imgGrip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:src="@drawable/dark_grip2"
android:contentDescription="@string/strReorder"/>
<CheckBox
android:id="@+id/chkParent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imgGrip"
android:checked="false"/>
<EditText
android:id="@+id/edtParent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/chkParent"
android:layout_toLeftOf="@+id/btnInsert"
android:textSize="17dip"
android:inputType="text"
android:hint="@string/strItem"/>
<ImageButton
android:id="@id/btnInsert"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_toLeftOf="@+id/viewParent"
android:src="@drawable/dark_content_new"
android:clickable="true"
android:hint="@string/strAddItemAbove"
android:contentDescription="@string/strAddItemAbove" />
<View
android:id="@id/viewParent"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_toLeftOf="@+id/btnExpand"/>
<ImageButton
android:id="@id/btnExpand"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:src="@drawable/dark_add_sub_item"
android:clickable="true"
android:hint="@string/strViewSubItems"
android:contentDescription="@string/strViewSubItems"/>
</RelativeLayout>
Thanks so much for any help!
The Android drag and drop framework enables you to add interactive drag and drop capabilities to your app. With drag and drop, users can copy or move text, images, objects—any content that can be represented by a URI—from one View to another within an app or, in multi-window mode, between apps.
The system sends the View object's listener a drag event with action type ACTION_DROP. Ended − Just after the action type ACTION_DROP, the system sends out a drag event with action type ACTION_DRAG_ENDED to indicate that the drag operation is over.
A drag and drop operation starts when the user makes a UI gesture that your app recognizes as a signal to start dragging data. In response, the app notifies the system that a drag and drop operation is starting. The system calls back to your app to get a representation of the data being dragged (a drag shadow).
Views that need to be notified of drag events must register OnDragListeners
. If they need to be aware when specific things or types of things move, for instance in order to change their appearance (either to a "DRAG ME HERE!" or a "DON'T YOU DARE DROP THAT ON ME!"). Even if your view is not a valid place to drop the object being dragged, if it needs to be 'drag context aware' so to speak, it registers an OnDragListener
and returns true forACTION_DRAG_STARTED
.
The one thing I'm missing from your very thorough question is what exactly you need to be cleaned up? If you just need to do some generic things that are in the scope of the activity, you might consider adding a different OnDragListener
to the base RelativeLayout
that registers for the events but waits for ACTION_DRAG_ENDED
to alert the activity to perform your generic cleanup code
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