Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unobtrusive popup that won't block drag n drop in Android

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.

enter image description here

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.

enter image description here

like image 242
Redone Avatar asked Dec 29 '15 15:12

Redone


2 Answers

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

like image 122
Jim Baca Avatar answered Sep 19 '22 08:09

Jim Baca


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. :)

like image 23
pRaNaY Avatar answered Sep 19 '22 08:09

pRaNaY