Android: Drag and drop a view onto another view


Understanding the Drag and the Drop

In enabling a user to perform a drop and drag action you have a view that is being dragged and a view that is being dragged to. The listener that will activate the drag, e.g. a long press, is attached to the view that will be dragged. While the view that is being dragged to has the drag listener attached to it. A single view can be dragged onto many different views if those views each have drag listeners set and ready to respond positively. Likewise multiple views can be dragged onto the same view that is being dragged to.

View Being Dragged To

Starting with the view being dragged to, which is an essential if you are going to be able to drop your draggable view anywhere, we have the following code. (See below for further explanation.)
// A view is identified from the layout file within which other views can be dragged (mainView)
RelativeLayout mainView = findViewById(R.id.mainView);

mainView.setOnDragListener(new View.OnDragListener() {
    @Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        
        switch (dragEvent.getAction()) {

            case ACTION_DRAG_STARTED:
                return true;
            case ACTION_DRAG_ENTERED:
                return true;
            case ACTION_DRAG_LOCATION:
                return true;
            case ACTION_DRAG_EXITED:
                return true;
            case ACTION_DROP:
                float newX = dragEvent.getX();
                float newY = dragEvent.getY(); 

                View dragView = (View) dragEvent.getLocalState();
    
                dragView.setX(newX - dragView.getWidth()/2);
                dragView.setY(newY - dragView.getHeight()/2);
                return true;
            case ACTION_DRAG_ENDED:
                return true;
            default:
                return false;

        }

    }


});

View Being Dragged

Next we have the view being dragged, which starts the process of dragging with the startDrag method. A convenient way to identify the desire to begin dragging is within a long click listener.
// A view is created that will be dragged. 
  ImageView newImg = new ImageView(this);
        newImg.setMinimumWidth(100);
        newImg.setMinimumHeight(100);
        newImg.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
        mainView.addView(newImg);

        imageView.setOnLongClickListener(new View.OnLongClickListener() {

    // Defines the one method for the interface, which is called when the View is long-clicked
    public boolean onLongClick(View v) {

        // Instantiates the drag shadow builder.
        View.DragShadowBuilder myShadow = new View.DragShadowBuilder(v);


        // Starts the drag
        v.startDrag(null,  // the data to be dragged
                myShadow,  // the drag shadow builder
                v,      // no need to use local data
                0          // flags (not currently used, set to 0)
        );
        return true;
    }


});
This is all there is to drag and drop, where the aim is to move a view of some kind to another position. To experiment this code can safely be placed within the opening onCreate method of the MainActivity.

Further Detail

The OnDragListener has a single method contained within its closure: onDrag (View v, DragEvent event).

The onDrag method receives a DragEvent instance, which identifies one of six actions

ACTION_DRAG_STARTED
ACTION_DRAG_ENTERED
ACTION_DRAG_LOCATION
ACTION_DROP
ACTION_DRAG_EXITED
ACTION_DRAG_ENDED

through a getAction() call. It is then your responsibility to response true or false as to whether the action has been handled. Returning true identifies that the action has been handled successfully, returning false indicates failure.

The view passed to the onDrag method is the view being dragged to, while the event holds all the DragEvent information.

We don’t have a direct reference to the view being dragged and you might think that you need to somehow code this information into the ClipData (which can be transported with the drag - see Android Developer Docs on Drag and Drop) but thankfully this isn’t the case because this information can be retrieved by casting the local state to a View.

This casting of the local state is a somewhat mysterious act because all we know about the local state from the docs is that:
The object is intended to provide local information about the drag and drop operation. For example, it can indicate whether the drag and drop operation is a copy or a move.
The local state is available only to views in the activity which has started the drag operation. In all other activities this method will return null
This method returns valid data for all event actions.
And so you would expect to work through an array of objects, or such like to arrive at the reference to the view being dragged. But no, a simple cast is all that is required, and presumably copy and move data is extracted in a similar way, but I have not yet investigated this.

Comments