I have been working on the launcher app for android similar to nova launcher. I have setup OnItemLongClickListener and OnDragListener. When i long click on an icon a popup is displayed with menu like "Remove", "Change Icon" etc. Following figure shows the progress of the app with popup open while Long click.
The problem is when the popup is opened the drag works but drop doesnot work. It seems that the i cannot log the x, y position once the popup is open. Also when the drop is performed the following message is shown in logcat.
I/ViewRootImpl: Reporting drop result: false
My code goes something like this in OnDragListener
public boolean onDrag(View v, DragEvent event) {
int dragEvent = event.getAction();
switch (dragEvent)
{
case DragEvent.ACTION_DRAG_LOCATION:
//Open popup here; note: its opened only once. popup.show();
//Log.i("Position x : ", Float.toString(event.getX())); log x or y
/*code to detect x any y change amount and close the popup
once user drags the icon little further and app knows that
user is trying to drag instead of opening the popup
and hence close the popup. popup.dismiss();
*/
// other case like ACTION_DROP etx goes after this
}
}
But it seems that after the popup is opened i cannot log x or y; also the code that determines if the action was intended for "drag" or "popup open", cannot be run.
So how do i solve this problem? I want to close the popup once the drag amount in any is sufficient to know that user wants to drag. And if not stop the drag and display the popup only.
Edit
I solved the problem with popup by using both OnTouchListner and OnDragListner. Following shows my code for OnDragListner.
//bottomAppDrawer is a GridView
bottomAppDrawer.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
int dragEvent = event.getAction();
LinearLayout draggedItem = (LinearLayout) event.getLocalState(); //dragged LinearLayout
GridView targetItem = (GridView) v; /* How do i get this drop target as LinearLayout so that i can delete or swap data */
switch (dragEvent)
{
case DragEvent.ACTION_DRAG_LOCATION:
if(reset==false) {
dragPositionStart = event.getX();
reset= true;
}
if(Math.abs(dragPositionStart - event.getX())>=20) {
Log.i("Position close : ", Float.toString(dragPositionStart));
if(isPopupOpen) {
popupMenu.dismiss();
v.startDrag(data, dragShadow, itemView, 0);
Toast.makeText(mContext, "popup closed", Toast.LENGTH_SHORT).show();
isPopupOpen = false;
}
reset = false;
}
break;
case DragEvent.ACTION_DROP:
Toast.makeText(mContext, "drop" + Integer.toString(targetItem.getChildCount()), Toast.LENGTH_SHORT).show();
break;
}
return true;
}
});
Now the problem is I am getting the drop target "Gridview" as I am dropping LinearLayout in "Gridview". Also this "LinearLayout is child of the "Gridview". And i want the drop target to be another "LinearLayout" inside the same "GridView". So that i can swap data or reorder. As in figure below.
From what I understand there are two things that you want to do. 1)Reorder the Views after drag. and 2)Change the View types after the reorder.
For problem 1 since it is a grid view it sounds like we really just want to reorder the data in the adapter, and possibly change the data to cause it to display differently. But we need to figure out the position of the original item and the target destination position.
we can extend the GridView to do that:
public class DragAndDropGridView extends GridView {
public void handleMove(int x, int y, int originalPosition) {
Rect rect = new Rect();
PushbackAdapter adapter = (PushbackAdapter) getAdapter();
for (int visiblePosition = getFirstVisiblePosition(); visiblePosition <= getLastVisiblePosition(); visiblePosition++) {
// TODO verify that there are no edge cases not covered by this
View view = getChildAt(visiblePosition);
int left = view.getLeft();
int top = view.getTop();
getChildVisibleRect(view, rect, null);
rect.offsetTo(left, top);
if (rect.contains(x, y)) {
// yay the user tried drop the view at this location
// determine if they wanted to drop it here or after this side
int centerX = rect.centerX();
if (x <= centerX) {
// we want to drop it here
adapter.move(originalPosition, visiblePosition);
adapter.notifyDataSetInvalidated();
break;
} else {
// we want to drop it behind
adapter.move(originalPosition, visiblePosition + 1);
adapter.notifyDataSetInvalidated();
break;
}
}
}
}
}
That leaves us with calling the handleMoveMethod. We do that from within the ACTION_DROP method.
case DragEvent.ACTION_DROP:
handleMove((int)event.getX(), (int)event.getY(), getPositionForView(draggedItem));
Toast.makeText(mContext, "drop" + Integer.toString(targetItem.getChildCount()), Toast.LENGTH_SHORT).show();
break;
Lastly(problem 2) it sounds like you may want to change either the content of the object at the position or the type of view it is contained in. I'd suggest using the getItemViewType and getItemViewTypeCount methods if you need to have different types of views. E.g. something along the lines of the following:
private static class PushbackAdapter extends ArrayAdapter {
ArrayList<Object> mItems;
public void move(int originalPosition, int targetPosition){
// TODO verify that this move logic is correct
Object item = mItems.remove(originalPosition);
item.useLinearLayoutType(true);
mItems.add(targetPosition, item);
}
...
@Override
public int getItemViewType(int i) {
return mItems.get(i).isLeanearLayoutType()? 1 : 0;
}
There could be bugs with this so please test thoroughly
Find position of LinerLayout(which dragging) in Gridview using targetItem.pointToPosition(..)
.
Swipe LinerLayout using below code:
int i =targetItem.pointToPosition((int)event.getX(), (int)event.getY());
int j = Integer.parseInt(event.getClipData().getItemAt(0).getText().toString());
Collections.swap(targetItem, i, j);//swap Linerlayout
Log.i(TAG, "Swapped " + i+ " with " + j);
Code is not tested. I hope its help you. :)
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