Monday, October 7, 2019

Android Drag and Drop with Examples

In android, Drag and Drop framework allow users to move a data from one view to another using graphical drag and drop gesture. 

The Drag and Drop framework will include following functionalities to support the data movement in android applications.

  • Drag Event Class
  • Drag Listeners
  • Helper Methods and Classes
Generally, the Drag and Drop process starts when user making gestures recognized as a signal to start dragging data and application tells the system that the drag is starting.

Once the drag is starting, the system call-back to our application to get the state of data being dragged and it sends drag events to the drag event listeners or call-back methods of each View in the layout.

Android Drag / Drop Process

In android, the Drag and Drop process contains a 4 steps or states, those are

  • Started
  • Continuing
  • Dropped
  • Ended

Started

This event will occur when we start dragging an item in layout and our application will call startDrag() method to tell the system to start a drag. The startDrag() method arguments will provide a data to be dragged, metadata for this data and a call-back for drawing the drag shadow.

The system will respond back to our application to get a drag shadow and it will display the drag shadow on the device.

After that, the system will send a drag event with action type ACTION_DRAG_STARTED to the drag event listeners for all the View objects in the current layout. To receive drag events continuously, including a possible drop event, the drag event listener must return true and it registers the listener with the system because only registered listeners continue to receive drag events. At this point, listeners can also change the appearance of their View object to show that the listener can accept a drop event.

In case if drag event listener returns false, then it won’t receive any drag events for the current operation until the system sends a drag event with action type ACTION_DRAG_ENDED. By sending false, the listener tells the system that it is not interested in the drag operation and does not want to accept the dragged data.

Continuing

When the user continues to drag, the drag shadow intersects with the bounding box of a View object and the system sends one or more drag events to the View object's drag event listener (In case if it is registered to receive events).
In response to the event, the listener may choose to alter its View object’s appearance. For example, if the event indicates that the drag shadow has entered the bounding box of the View (action type ACTION_DRAG_ENTERED), the listener can react by highlighting its View.

Dropped

Whenever the user releases the drag shadow within the bounding box of a View that can accept the data. The system sends the View object's listener a drag event with action type ACTION_DROP.

The drag event will contain the data that is passed to the system while starting the operation by using startDrag() method and the listener will return true to the system in case if drop success.

Ended

After completion of 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. This is done regardless of where the user released the drag shadow.

Drag Event Listener and Call-back Method

In android, the View object receives a drag events either from a drag event listener that implements View.OnDragListener or with its onDragEvent(DragEvent) call-back method. When the system calls a method or listener, it passes to a DragEvent object.

We can use both listener and call-back method for View objects but in most cases listener is preferable. In case if we use both method and listener, first system calls the listener and then defined call-back method as long as the listener returns true.

The combination of onDragEvent(DragEvent) method and View.OnDragListener is analogous to the combination of onTouchEvent() and View.OnTouchListener used with touch events.

Android Drag Events

Generally, the system sends a drag event object (DragEvent) to perform drag / drop process and the DragEvent object contains an action type that tells the listener what is happening in the drag / drop process.

The listener calls getAction() method to get the action type from DragEvent object. Following are the different type of action types available in DragEvent object.

Action TypeDescription
ACTION_DRAG_STARTEDA View object's drag event listener receives this action type event just after the application calls startDrag() and gets a drag shadow.
ACTION_DRAG_ENTEREDA View object's drag event listener receives this action type when the drag shadow has entered the bounding box of the View. This is the first action type event, the listener receives when the drag shadow enters the bounding box.
ACTION_DRAG_LOCATIONA View object's drag event listener receives this action type event after it receives an ACTION_DRAG_ENTERED event while the drag shadow is still within the bounding box of the View.
ACTION_DRAG_EXITEDA View object's drag event listener receives this action type event after it receives an ACTION_DRAG_ENTERED and at least one ACTION_DRAG_LOCATION event, and after the user has moved the drag shadow outside the bounding box of the View.
ACTION_DROPA View object's drag event listener receives this action type event when the user releases the drag shadow over the View object.
ACTION_DRAG_ENDEDA View object's drag event listener receives this action type event when the system is ending the drag operation.
The DragEvent object also contains the data that our application provided to the system in the call to startDrag(). Some of the data is valid only for certain action types.

Android Drag Shadow

In android, while performing a drag and drop operation, the system will display an image that the user drags and we can call it as a drag shadow. During the data movement, images are used to represent that the data being dragged.

By using View.DragShadowBuilder object methods, we can display an image of View that the user drags and then pass it to the system when we start a drag using startDrag() method. As part of its response to startDrag(), the system invokes the methods that we defined in View.DragShadowBuilder to obtain a drag shadow.

Android Designing a Drag and Drop Operation

Following is the step-by-step how to start a drag event, how to respond to events during the drag, how respond to a drop event, and how to end the drag and drop operation.

Starting a Drag Event

In android, we can start a drag event by using a drag gesture, usually a long press on View object. In response, we need to do the following things.

We need to create a ClipData and ClipData.item for the data that is being moved. As a part of the ClipData object, we need to send a metadata that is stored in a ClipDescription object within the ClipData. For a drag and drop operation that does not represent data movement, we may need to use null instead of an actual object.

After that, we need to use View.DragShadowBuilder(View) to create a drag shadow for the View objects that is being moved. The View.DragShadowBuilder will create a default drag shadow that’s same size as the View argument passed to it. In case, if we want to customize the drag shadow, then we need to extend the View.DragShadowBuilder functionality based on our requirements.

For example, following is the code snippet which shows how to respond for a long press on View object by creating a ClipData object that contains the tag or label of a View object.

@Overridepublic boolean onLongClick(View v) {
    // Create a new ClipData.Item from the View object's tag
    
ClipData.Item item = new ClipData.Item((CharSequence) v.getTag());

    
// Create a new ClipData using the tag as a label, the plain text MIME type, and
    // the already-created item. This will create a new ClipDescription object within the
    // ClipData, and set its MIME type entry to "text/plain"
    
String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
    ClipData data = 
new ClipData(v.getTag().toString(), mimeTypes, item);

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

    
// Starts the drag
    
v.startDrag(data       // data to be dragged
            
, dragshadow  // drag shadow
            
, v            // local data about the drag and drop operation
            
0          // flags set to 0 because not using currently
    
);
    
return true;
}
This is how we can respond for a long press on View object by creating a ClipData object in android applications.

Responding to a Drag Start Event

During the drag operation, the system dispatches drag events to the drag event listeners of View objects in the current layout. The listeners should react by calling getAction() to get the action type. At the start of a drag, this methods returns ACTION_DRAG_STARTED.

In response to an event with the action type ACTION_DRAG_STARTED, the listener will do the following things.

  • The listener will call getClipDescription() to get the ClipDescription details and it uses a MIME type methods in ClientDescription to decide whether to accept the data being dragged or not. In case if the drag and drop operation does not represent any data movement, then it does not required.
  • If the listener can accept a drop, it should return true. This tells the system to continue to send drag events to the listener. If it can't accept a drop, it should return false, and the system will stop sending drag events until it sends out ACTION_DRAG_ENDED.

Handling an Events during the Drag

During the drag, listeners primarily use drag events such as ACTION_DRAG_ENTERED, ACTION_DRAG_LOCATION, ACTION_DRAG_EXITED, etc. to decide whether to change the appearance of the View to indicate that it is about to receive a drop.

Responding to a Drop Event

Whenever we release a drag shadow on the View that accept a content being dragged, the system dispatches a drag event to that View with action type ACTION_DROP and the listener will do the following things.

  • The listener will call getClipData() to get the ClipData object that was originally supplied in the call to startDrag() and store it. If the drag and drop operation does not represent data movement, this may not be necessary.
  • It will return either true or false to indicate that whether the drop was processed successfully or not and it return the value from getResult() method in ACTION_DRAG_ENDED event.

Responding to a Drag End

Whenever we release the drag shadow, the system will send a drag event to all the drag event listeners in our application, with an action type of ACTION_DRAG_ENDED to indicate that the drag operation is over and the listener will do the following things.

  • During the operation, in case if the listener changed its View object's appearance, it should reset the View to its default appearance to indicate that the operation is over.
  • The listener can optionally call getResult() to find out more about the operation.
For example, following is the code snippet which shows how to respond for drag events in a listener.

// This is the method that the system calls when it dispatches a drag event to the listener.@Overridepublic boolean onDrag(View v, DragEvent event) {
    
// Defines a variable to store the action type for the incoming event
    
int action = event.getAction();
    
// Handles each of the expected events
    
switch (action) {

        
case DragEvent.ACTION_DRAG_STARTED:
            // Determines if this View can accept the dragged data
            
if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                
// applies a blue color tint to the View to indicate that it can accept the data
                  
v.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
                // Invalidate the view to force a redraw in the new tint
                  
v.invalidate();
                
// returns true to indicate that the View can accept the dragged data.
                
return true;
            }
            
// Returns false. During the current drag and drop operation, this View will
            // not receive events again until ACTION_DRAG_ENDED is sent.
            
return false;

        
case DragEvent.ACTION_DRAG_ENTERED:

            
// Applies a YELLOW or any color tint to the View. Return true; the return value is ignored.
            
v.getBackground().setColorFilter(Color.YELLOW, PorterDuff.Mode.SRC_IN);
            
// Invalidate the view to force a redraw in the new tint
            
v.invalidate();
            
return true;

        
case DragEvent.ACTION_DRAG_LOCATION:
            
// Ignore the event
            
return true;

        
case DragEvent.ACTION_DRAG_EXITED:
            // Re-sets the color tint to blue, if you had set the BLUE color or any color in ACTION_DRAG_STARTED. Returns true; the return value is ignored.
            
v.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
            //If u had not provided any color in ACTION_DRAG_STARTED then clear color filter.
            
v.getBackground().clearColorFilter();
            
// Invalidate the view to force a redraw in the new tint
            
v.invalidate();
            
return true;
        
case DragEvent.ACTION_DROP:
            
// Gets the item containing the dragged data
            
ClipData.Item item = event.getClipData().getItemAt(0);
            
// Gets the text data from the item.
            
String dragData = item.getText().toString();
            
// Displays a message containing the dragged data.
            
Toast.makeText(this"Dragged data is " + dragData, Toast.LENGTH_SHORT).show();
            
// Turns off any color tints
            
v.getBackground().clearColorFilter();
            
// Invalidates the view to force a redraw
            
v.invalidate();
            
// Returns true. DragEvent.getResult() will return true.
            
return true;
        
case DragEvent.ACTION_DRAG_ENDED:
            
// Turns off any color tinting
            
v.getBackground().clearColorFilter();
            
// Invalidates the view to force a redraw
            
v.invalidate();
            
// Does a getResult(), and displays what happened.
            
if (event.getResult())
                Toast.makeText(
this"The drop was handled.", Toast.LENGTH_SHORT).show();
            
else
                
Toast.makeText(this"The drop didn't work.", Toast.LENGTH_SHORT).show();
            
// returns true; the value is ignored.
            
return true;

        
// An unknown action type was received.
        
default:
            Log.e(
"DragDrop Example""Unknown action type received by OnDragListener.");
            
break;
    }
    
return false;
}
This is how we can implement the drag and drop operation in our applications based on our requirements.

Now we will see how to drag and drop View objects such as button, imageview or textview, etc. in android applications with example. 

Android Drag and Drop Example

Following is the example of defining a UI controls such as button, imageview and textview in LinearLayout to implement a drag and drop functionality in android application.

Create a new android application using android studio and give names as DragDropExample. In case if you are not aware of creating an app in android studio check this article Android Hello World App.

Now open an activity_main.xml file from \res\layout path and write the code like as shown below

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    
android:layout_width="match_parent"
    
android:layout_height="match_parent"
    
android:orientation="vertical">
    <
LinearLayout android:id="@+id/layout1"
        
android:layout_width="match_parent"
        
android:layout_height="match_parent"
        
android:layout_weight="1"
        
android:background="#EE501B"
        
android:gravity="center"
        
android:orientation="vertical">
        <
TextView android:id="@+id/lbl"
            
android:layout_width="wrap_content"
            
android:layout_height="wrap_content"
            
android:text="Draggable Text"
            
android:textSize="20sp" />
        <
ImageView android:id="@+id/ingvw"
            
android:layout_width="wrap_content"
            
android:layout_height="wrap_content"
            
android:src="@mipmap/ic_launcher" />
        <
Button android:id="@+id/btnDrag"
            
android:layout_width="wrap_content"
            
android:layout_height="wrap_content"
            
android:text="Draggable Button" />
    </
LinearLayout>
    <
LinearLayout android:id="@+id/layout2"
        
android:layout_width="match_parent"
        
android:layout_height="match_parent"
        
android:layout_weight="1"
        
android:background="#00ADEF"
        
android:gravity="center"
        
android:orientation="vertical" />
    <
LinearLayout android:id="@+id/layout3"
        
android:layout_width="match_parent"
        
android:layout_height="match_parent"
        
android:layout_weight="1"
        
android:background="#80CC28"
        
android:gravity="center"
        
android:orientation="vertical" />
</
LinearLayout>
If you observe above code we created a multiple linear layouts with required UI controls to perform drag and drop execution.

Once we are done with creation of layouts with required controls, we need to load the XML layout resource from our activity onCreate() callback method, for that open main activity file MainActivity.java from \java\com.tutlane.dragdropexample path and write the code like as shown below.

MainActivity.java

package com.tutlane.draganddropexample;import android.content.ClipData;import android.content.ClipDescription;import android.graphics.Color;import android.graphics.PorterDuff;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.DragEvent;import android.view.View;import android.view.ViewGroup;import android.widget.Button;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnDragListener, View.OnLongClickListener {
    
@Override
    
protected void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);
        
//Find all views and set Tag to all draggable views
        
TextView txtVw = (TextView) findViewById(R.id.lbl);
        txtVw.setTag(
"DRAGGABLE TEXTVIEW");
        txtVw.setOnLongClickListener(
this);
        ImageView imgVw = (ImageView) findViewById(R.id.
ingvw);
        imgVw.setTag(
"ANDROID ICON");
        imgVw.setOnLongClickListener(
this);
        Button btn = (Button) findViewById(R.id.
btnDrag);
        btn.setTag(
"DRAGGABLE BUTTON");
        btn.setOnLongClickListener(
this);
        
//Set Drag Event Listeners for defined layouts
        
findViewById(R.id.layout1).setOnDragListener(this);
        findViewById(R.id.
layout2).setOnDragListener(this);
        findViewById(R.id.
layout3).setOnDragListener(this);
    }
    
@Override
    
public boolean onLongClick(View v) {
        
// Create a new ClipData.Item from the ImageView object's tag
        
ClipData.Item item = new ClipData.Item((CharSequence) v.getTag());
        
// Create a new ClipData using the tag as a label, the plain text MIME type, and
        // the already-created item. This will create a new ClipDescription object within the
        // ClipData, and set its MIME type entry to "text/plain"
        
String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
        ClipData data = 
new ClipData(v.getTag().toString(), mimeTypes, item);
        
// Instantiates the drag shadow builder.
        
View.DragShadowBuilder dragshadow = new View.DragShadowBuilder(v);
        
// Starts the drag
        
v.startDrag(data        // data to be dragged
                
, dragshadow   // drag shadow builder
                
, v           // local data about the drag and drop operation
                
0          // flags (not currently used, set to 0)
        
);
        
return true;
    }
    
// This is the method that the system calls when it dispatches a drag event to the listener.
    
@Override
    
public boolean onDrag(View v, DragEvent event) {
        
// Defines a variable to store the action type for the incoming event
        
int action = event.getAction();
        
// Handles each of the expected events
        
switch (action) {

            
case DragEvent.ACTION_DRAG_STARTED:
                
// Determines if this View can accept the dragged data
                
if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                    
// if you want to apply color when drag started to your view you can uncomment below lines
                    // to give any color tint to the View to indicate that it can accept data.
                    // v.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
                    // Invalidate the view to force a redraw in the new tint
                    //  v.invalidate();
                    // returns true to indicate that the View can accept the dragged data.
                    
return true;
                }
                
// Returns false. During the current drag and drop operation, this View will
                // not receive events again until ACTION_DRAG_ENDED is sent.
                
return false;

            
case DragEvent.ACTION_DRAG_ENTERED:
                
// Applies a GRAY or any color tint to the View. Return true; the return value is ignored.
                
v.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN);
                
// Invalidate the view to force a redraw in the new tint
                
v.invalidate();
                
return true;

            
case DragEvent.ACTION_DRAG_LOCATION:
                
// Ignore the event
                
return true;

            
case DragEvent.ACTION_DRAG_EXITED:
                
// Re-sets the color tint to blue. Returns true; the return value is ignored.
                // view.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
                //It will clear a color filter .
                
v.getBackground().clearColorFilter();
                
// Invalidate the view to force a redraw in the new tint
                
v.invalidate();
                
return true;

            
case DragEvent.ACTION_DROP:
                
// Gets the item containing the dragged data
                
ClipData.Item item = event.getClipData().getItemAt(0);
                
// Gets the text data from the item.
                
String dragData = item.getText().toString();
                
// Displays a message containing the dragged data.
                
Toast.makeText(this"Dragged data is " + dragData, Toast.LENGTH_SHORT).show();
                
// Turns off any color tints
                
v.getBackground().clearColorFilter();
                
// Invalidates the view to force a redraw
                
v.invalidate();

                View vw = (View) event.getLocalState();
                ViewGroup owner = (ViewGroup) vw.getParent();
                owner.removeView(vw); 
//remove the dragged view
                //caste the view into LinearLayout as our drag acceptable layout is LinearLayout
                
LinearLayout container = (LinearLayout) v;
                container.addView(vw);
//Add the dragged view
                
vw.setVisibility(View.VISIBLE);//finally set Visibility to VISIBLE
                // Returns true. DragEvent.getResult() will return true.
                
return true;

            
case DragEvent.ACTION_DRAG_ENDED:
                
// Turns off any color tinting
                
v.getBackground().clearColorFilter();
                
// Invalidates the view to force a redraw
                
v.invalidate();
                
// Does a getResult(), and displays what happened.
                
if (event.getResult())
                    Toast.makeText(
this"The drop was handled.", Toast.LENGTH_SHORT).show();
                
else
                    
Toast.makeText(this"The drop didn't work.", Toast.LENGTH_SHORT).show();
                
// returns true; the value is ignored.
                
return true;
            
// An unknown action type was received.
            
default:
                Log.e(
"DragDrop Example""Unknown action type received by OnDragListener.");
                
break;
        }
        
return false;
    }
}
If you observe above code, we registered our View objects and implemented a required Drag and Long press events to implement drag and drop functionality.

Generally, during the launch of our activityonCreate() callback method will be called by android framework to get the required layout for an activity.

Output of Android Drag and Drop Example

When we run above example using android virtual device (AVD) we will get a result like as shown below.

Android Drag and Drop Example Result

If you observe above result, we are able to drag and drop View’s such as Button, Imageview and Textview to different layouts in our application.

This is how we can implement drag and drop functionality in our application to move data from one view to another based on our requirements.

No comments:

Post a Comment

How to DROP SEQUENCE in Oracle?

  Oracle  DROP SEQUENCE   overview The  DROP SEQUENCE  the statement allows you to remove a sequence from the database. Here is the basic sy...