From dd05f1812ee86bc8c6176c9dbd30aae8fe0db8b8 Mon Sep 17 00:00:00 2001 From: Joe Malin Date: Wed, 19 Jan 2011 14:33:23 -0800 Subject: [PATCH] Doc Change: Topics for copy/paste/drag/drop Change-Id: I755216fe9d2afca87a9adaeb99840142ff34a685 --- docs/html/guide/guide_toc.cs | 49 +- .../html/guide/topics/clipboard/copy-paste.jd | 1094 +++++++++++++++++ docs/html/guide/topics/ui/drag-drop.jd | 995 +++++++++++++++ .../ui/clipboard/copy_paste_framework.png | Bin 0 -> 37996 bytes 4 files changed, 2119 insertions(+), 19 deletions(-) create mode 100644 docs/html/guide/topics/clipboard/copy-paste.jd create mode 100644 docs/html/guide/topics/ui/drag-drop.jd create mode 100755 docs/html/images/ui/clipboard/copy_paste_framework.png diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index c52fd6acd73c8..a2a2be9d77089 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -125,7 +125,12 @@ Creating Status Bar Notifications - + +
  • + + Dragging and Dropping + new! +
  • Applying Styles and Themes
  • @@ -248,6 +253,12 @@ +
  • + + Copying and Pasting + + new! +
  • Audio and Video
  • @@ -399,24 +410,24 @@
  • - Managing Virtual Devices - + Creating and Managing Virtual Devices +
  • @@ -425,7 +436,7 @@ Using Hardware Devices - +
  • @@ -440,7 +451,7 @@
  • - From the Command Line + On the Command Line
  • @@ -470,12 +481,12 @@ diff --git a/docs/html/guide/topics/clipboard/copy-paste.jd b/docs/html/guide/topics/clipboard/copy-paste.jd new file mode 100644 index 0000000000000..9a50a351a2b0d --- /dev/null +++ b/docs/html/guide/topics/clipboard/copy-paste.jd @@ -0,0 +1,1094 @@ +page.title=Copying and Pasting +@jd:body +
    +
    +

    Quickview

    +
      +
    • + A clipboard-based framework for copying and pasting data. +
    • +
    • + Supports both simple and complex data, including text strings, complex data + structures, text and binary stream data, and application assets. +
    • +
    • + Copies and pastes simple text directly to and from the clipboard. +
    • +
    • + Copies and pastes complex data using a content provider. +
    • +
    • + Requires API 11. +
    • +
    +

    In this document

    +
      +
    1. + The Clipboard Framework +
    2. +
    3. + Clipboard Classes +
        +
      1. + ClipboardManager +
      2. +
      3. + + ClipData, ClipDescription, and ClipData.Item + +
      4. +
      5. + ClipData convenience methods +
      6. +
      7. + Coercing the clipboard data to text +
      8. +
      +
    4. +
    5. + Copying to the Clipboard +
    6. +
    7. + Pasting from the Clipboard +
        +
      1. + Pasting plain text +
      2. +
      3. + Pasting data from a content URI +
      4. +
      5. + Pasting an Intent +
      6. +
      +
    8. +
    9. + Using Content Providers to Copy Complex Data +
        +
      1. + Encoding an identifier on the URI +
      2. +
      3. + Copying data structures +
      4. +
      5. + Copying data streams +
      6. +
      +
    10. +
    11. + Designing Effective Copy/Paste Functionality +
    12. +
    +

    Key classes

    +
      +
    1. + {@link android.content.ClipboardManager ClipboardManager} +
    2. +
    3. + {@link android.content.ClipData ClipData} +
    4. +
    5. + {@link android.content.ClipData.Item ClipData.Item} +
    6. +
    7. + {@link android.content.ClipDescription ClipDescription} +
    8. +
    9. + {@link android.net.Uri Uri} +
    10. +
    11. + {@link android.content.ContentProvider} +
    12. +
    13. + {@link android.content.Intent Intent} +
    14. +
    +

    Related Samples

    +
      +
    1. + + Note Pad sample application +
    2. +
    +

    See also

    +
      +
    1. + Content Providers +
    2. +
    +
    +
    +

    + Android provides a powerful clipboard-based framework for copying and pasting. It + supports both simple and complex data types, including text strings, complex data + structures, text and binary stream data, and even application assets. Simple text data is stored + directly in the clipboard, while complex data is stored as a reference that the pasting + application resolves with a content provider. Copying and pasting works both within an + application and between applications that implement the framework. +

    + +

    + Since a part of the framework uses content providers, this topic assumes some + familiarity with the Android Content Provider API, which is described in the topic + Content Providers. +

    +

    The Clipboard Framework

    +

    + When you use the clipboard framework, you put data into a clip object, and then + put the clip object on the system-wide clipboard. The clip object can take one of three forms: +

    +
    +
    Text
    +
    + A text string. You put the string directly into the clip object, which you then put onto + the clipboard. To paste the string, you get the clip object from the clipboard and copy + the string to into your application's storage. +
    +
    URI
    +
    + A {@link android.net.Uri} object representing any form of URI. This is primarily for + copying complex data from a content provider. To copy data, you put a + {@link android.net.Uri} object into a clip object and put the clip object onto + the clipboard. To paste the data, you get the clip object, get the + {@link android.net.Uri} object, resolve it to a data source such as a content provider, + and copy the data from the source into your application's storage. +
    +
    Intent
    +
    + An {@link android.content.Intent}. This supports copying application shortcuts. To copy + data, you create an Intent, put it into a clip object, and put the clip object onto the + clipboard. To paste the data, you get the clip object and then copy the Intent object + into your application's memory area. +
    +
    +

    + The clipboard holds only one clip object at a time. When an application puts a clip object on + the clipboard, the previous clip object disappears. +

    +

    + If you want to allow users to paste data into your application, you don't have to handle all + types of data. You can examine the data on the clipboard before you give users the option to + paste it. Besides having a certain data form, the clip object also contains metadata that tells + you what MIME type or types are available. This metadata helps you decide if your application + can do something useful with the clipboard data. For example, if you have an application that + primarily handles text you may want to ignore clip objects that contain a URI or Intent. +

    +

    + You may also want to allow users to paste text regardless of the form of data on the + clipboard. To do this, you can force the clipboard data into a text representation, and then + paste this text. This is described in the section Coercing the + clipboard to text. +

    +

    Clipboard Classes

    +

    + This section describes the classes used by the clipboard framework. +

    +

    ClipboardManager

    +

    + In the Android system, the system clipboard is represented by the global + {@link android.content.ClipboardManager} class. You do not instantiate this + class directly; instead, you get a reference to it by invoking + {@link android.content.Context#getSystemService(String) getSystemService(CLIPBOARD_SERVICE)}. +

    +

    ClipData, ClipData.Item, and ClipDescription

    +

    + To add data to the clipboard, you create a {@link android.content.ClipData} object that + contains both a description of the data and the data itself. The clipboard holds only one + {@link android.content.ClipData} at a time. A {@link android.content.ClipData} contains a + {@link android.content.ClipDescription} object and one or more + {@link android.content.ClipData.Item} objects. +

    +

    + A {@link android.content.ClipDescription} object contains metadata about the clip. In + particular, it contains an array of available MIME types for the clip's data. When you put a + clip on the clipboard, this array is available to pasting applications, which can examine it to + see if they can handle any of available the MIME types. +

    +

    + A {@link android.content.ClipData.Item} object contains the text, URI, or Intent data: +

    +
    +
    Text
    +
    + A {@link java.lang.CharSequence}. +
    +
    URI
    +
    + A {@link android.net.Uri}. This usually contains a content provider URI, although any + URI is allowed. The application that provides the data puts the URI on the clipboard. + Applications that want to paste the data get the URI from the clipboard and use it to + access the content provider (or other data source) and retrieve the data. +
    +
    Intent
    +
    + An {@link android.content.Intent}. This data type allows you to copy an application shortcut + to the clipboard. Users can then paste the shortcut into their applications for later use. +
    +
    +

    + You can add more than one {@link android.content.ClipData.Item} object to a clip. This allows + users to copy and paste multiple selections as a single clip. For example, if you have a list + widget that allows the user to select more than one item at a time, you can copy all the items + to the clipboard at once. To do this, you create a separate + {@link android.content.ClipData.Item} for each list item, and then you add the + {@link android.content.ClipData.Item} objects to the {@link android.content.ClipData} object. +

    +

    ClipData convenience methods

    +

    + The {@link android.content.ClipData} class provides static convenience methods for creating + a {@link android.content.ClipData} object with a single {@link android.content.ClipData.Item} + object and a simple {@link android.content.ClipDescription} object: +

    +
    +
    +{@link android.content.ClipData#newPlainText(CharSequence,CharSequence) newPlainText(label, text)} +
    +
    + Returns a {@link android.content.ClipData} object whose single + {@link android.content.ClipData.Item} object contains a text string. The + {@link android.content.ClipDescription} object's label is set to label. + The single MIME type in {@link android.content.ClipDescription} is + {@link android.content.ClipDescription#MIMETYPE_TEXT_PLAIN}. +

    + Use +{@link android.content.ClipData#newPlainText(CharSequence,CharSequence) newPlainText()} + to create a clip from a text string. +

    +
    +{@link android.content.ClipData#newUri(ContentResolver, CharSequence, Uri) newUri(resolver, label, URI)} +
    +
    + Returns a {@link android.content.ClipData} object whose single + {@link android.content.ClipData.Item} object contains a URI. The + {@link android.content.ClipDescription} object's label is set to label. + If the URI is a content URI ({@link android.net.Uri#getScheme() Uri.getScheme()} returns + content:), the method uses the {@link android.content.ContentResolver} object + provided in resolver to retrieve the available MIME types from the + content provider and store them in {@link android.content.ClipDescription}. For a URI that + is not a content: URI, the method sets the MIME type to + {@link android.content.ClipDescription#MIMETYPE_TEXT_URILIST}. +

    + Use +{@link android.content.ClipData#newUri(ContentResolver, CharSequence, Uri) newUri()} + to create a clip from a URI, particularly a content: URI. +

    +
    +
    + {@link android.content.ClipData#newIntent(CharSequence, Intent) newIntent(label, intent)} +
    +
    + Returns a {@link android.content.ClipData} object whose single + {@link android.content.ClipData.Item} object contains an {@link android.content.Intent}. + The {@link android.content.ClipDescription} object's label is set to label. + The MIME type is set to {@link android.content.ClipDescription#MIMETYPE_TEXT_INTENT}. +

    + Use +{@link android.content.ClipData#newIntent(CharSequence, Intent) newIntent()} + to create a clip from an Intent object. +

    +
    +

    Coercing the clipboard data to text

    +

    + Even if your application only handles text, you can copy non-text data from the + clipboard by converting it with the method + {@link android.content.ClipData.Item#coerceToText(Context) ClipData.Item.coerceToText()}. +

    +

    + This method converts the data in {@link android.content.ClipData.Item} to text and + returns a {@link java.lang.CharSequence}. The value that + {@link android.content.ClipData.Item#coerceToText(Context) ClipData.Item.coerceToText()} + returns is based on the form of data in {@link android.content.ClipData.Item}: +

    +
    +
    Text
    +
    + If {@link android.content.ClipData.Item} is text + ({@link android.content.ClipData.Item#getText()} is not null), + {@link android.content.ClipData.Item#coerceToText(Context) coerceToText()} returns the + text. +
    +
    URI
    +
    + If {@link android.content.ClipData.Item} is a URI + ({@link android.content.ClipData.Item#getUri()} is not null), + {@link android.content.ClipData.Item#coerceToText(Context) coerceToText()} tries to use + it as a content URI: +
      +
    • + If the URI is a content URI and the provider can return a text stream, + {@link android.content.ClipData.Item#coerceToText(Context) coerceToText()} returns + a text stream. +
    • +
    • + If the URI is a content URI but the provider does not offer a text stream, + {@link android.content.ClipData.Item#coerceToText(Context) coerceToText()} returns + a representation of the URI. The representation is the same as that returned by + {@link android.net.Uri#toString() Uri.toString()}. +
    • +
    • + If the URI is not a content URI, + {@link android.content.ClipData.Item#coerceToText(Context) coerceToText()} returns + a representation of the URI. The representation is the same as that returned by + {@link android.net.Uri#toString() Uri.toString()}. +
    • +
    +
    +
    Intent
    +
    + If {@link android.content.ClipData.Item} is an Intent + ({@link android.content.ClipData.Item#getIntent()} is not null), + {@link android.content.ClipData.Item#coerceToText(Context) coerceToText()} converts it to + an Intent URI and returns it. The representation is the same as that returned by + {@link android.content.Intent#toUri(int) Intent.toUri(URI_INTENT_SCHEME)}. +
    +
    +

    + The clipboard framework is summarized in Figure 1. To copy data, an application puts a + {@link android.content.ClipData} object on the {@link android.content.ClipboardManager} global + clipboard. The {@link android.content.ClipData} contains one or more + {@link android.content.ClipData.Item} objects and one + {@link android.content.ClipDescription} object. To paste data, an application gets the + {@link android.content.ClipData}, gets its MIME type from the + {@link android.content.ClipDescription}, and gets the data either from + the {@link android.content.ClipData.Item} or from the content provider referred to by + {@link android.content.ClipData.Item}. +

    + + A block diagram of the copy and paste framework +

    + Figure 1. The Android clipboard framework +

    +

    Copying to the Clipboard

    +

    + As described previously, to copy data to the clipboard you get a handle to the global + {@link android.content.ClipboardManager} object, create a {@link android.content.ClipData} + object, add a {@link android.content.ClipDescription} and one or more + {@link android.content.ClipData.Item} objects to it, and add the finished + {@link android.content.ClipData} object to the {@link android.content.ClipboardManager} object. + This is described in detail in the following procedure: +

    +
      +
    1. + If you are copying data using a content URI, set up a content + provider. +

      + The + Note Pad sample application is an example of using a content provider for + copying and pasting. The + + NotePadProvider class implements the content provider. The + + NotePad class defines a contract between the provider and other applications, + including the supported MIME types. +

      +
    2. +
    3. + Get the system clipboard: +
      +
      +...
      +
      +// if the user selects copy
      +case R.id.menu_copy:
      +
      +// Gets a handle to the clipboard service.
      +ClipboardManager clipboard = (ClipboardManager)
      +        getSystemService(Context.CLIPBOARD_SERVICE);
      +
      +
    4. +
    5. +

      + Copy the data to a new {@link android.content.ClipData} object: +

      +
        +
      • +

        For text

        +
        +// Creates a new text clip to put on the clipboard
        +ClipData clip = ClipData.newPlainText("simple text","Hello, World!");
        +
        +
      • +
      • +

        For a URI

        +

        + This snippet constructs a URI by encoding a record ID onto the content URI + for the provider. This technique is covered in more detail + in the section Encoding an identifier on the URI: +

        +
        +// Creates a Uri based on a base Uri and a record ID based on the contact's last name
        +// Declares the base URI string
        +private static final String CONTACTS = "content://com.example.contacts";
        +
        +// Declares a path string for URIs that you use to copy data
        +private static final String COPY_PATH = "/copy";
        +
        +// Declares the Uri to paste to the clipboard
        +Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);
        +
        +...
        +
        +// Creates a new URI clip object. The system uses the anonymous getContentResolver() object to
        +// get MIME types from provider. The clip object's label is "URI", and its data is
        +// the Uri previously created.
        +ClipData clip = ClipData.newUri(getContentResolver(),"URI",copyUri);
        +
        +
      • +
      • +

        For an Intent

        +

        + This snippet constructs an Intent for an application + and then puts it in the clip object: +

        +
        +// Creates the Intent
        +Intent appIntent = new Intent(this, com.example.demo.myapplication.class);
        +
        +...
        +
        +// Creates a clip object with the Intent in it. Its label is "Intent" and its data is
        +// the Intent object created previously
        +ClipData clip = ClipData.newIntent("Intent",appIntent);
        +
        +
      • +
      +
    6. +
    7. + Put the new clip object on the clipboard: +
      +// Set the clipboard's primary clip.
      +clipboard.setPrimaryClip(clip);
      +
      +
    8. +
    +

    Pasting from the Clipboard

    +

    + As described previously, you paste data from the clipboard by getting the global clipboard + object, getting the clip object, looking at its data, and if possible copying the data from + the clip object to your own storage. This section describes in detail how to do this for + the three forms of clipboard data. +

    +

    Pasting plain text

    +

    + To paste plain text, first get the global clipboard and verify that it can return plain text. + Then get the clip object and copy its text to your own storage using + {@link android.content.ClipData.Item#getText()}, as described in the following procedure: +

    +
      +
    1. + Get the global {@link android.content.ClipboardManager} object using + {@link android.content.Context#getSystemService(String) getSystemService(CLIPBOARD_SERVICE)}. Also + declare a global variable to contain the pasted text: +
      +ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
      +
      +String pasteData = "";
      +
      +
      +
    2. +
    3. + Next, determine if you should enable or disable the "paste" option in the + current Activity. You should verify that the clipboard contains a clip and that you + can handle the type of data represented by the clip: +
      +// Gets the ID of the "paste" menu item
      +MenuItem mPasteItem = menu.findItem(R.id.menu_paste);
      +
      +// If the clipboard doesn't contain data, disable the paste menu item.
      +// If it does contain data, decide if you can handle the data.
      +if (!(clipboard.hasPrimaryClip())) {
      +
      +    mPasteItem.setEnabled(false);
      +
      +    } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {
      +
      +        // This disables the paste menu item, since the clipboard has data but it is not plain text
      +        mPasteItem.setEnabled(false);
      +    } else {
      +
      +        // This enables the paste menu item, since the clipboard contains plain text.
      +        mPasteItem.setEnabled(true);
      +    }
      +}
      +
      +
    4. +
    5. + Copy the data from the clipboard. This point in the program is only reachable if the + "paste" menu item is enabled, so you can assume that the clipboard contains + plain text. You do not yet know if it contains a text string or a URI that points to plain + text. The following snippet tests this, but it only shows the code for handling plain text: +
      +// Responds to the user selecting "paste"
      +case R.id.menu_paste:
      +
      +// Examines the item on the clipboard. If getText() does not return null, the clip item contains the
      +// text. Assumes that this application can only handle one item at a time.
      + ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
      +
      +// Gets the clipboard as text.
      +pasteData = item.getText();
      +
      +// If the string contains data, then the paste operation is done
      +if (pasteData != null) {
      +    return;
      +
      +// The clipboard does not contain text. If it contains a URI, attempts to get data from it
      +} else {
      +    Uri pasteUri = item.getUri();
      +
      +    // If the URI contains something, try to get text from it
      +    if (pasteUri != null) {
      +
      +        // calls a routine to resolve the URI and get data from it. This routine is not
      +        // presented here.
      +        pasteData = resolveUri(Uri);
      +        return;
      +    } else {
      +
      +    // Something is wrong. The MIME type was plain text, but the clipboard does not contain either
      +    // text or a Uri. Report an error.
      +    Log.e("Clipboard contains an invalid data type");
      +    return;
      +    }
      +}
      +
      +
    6. +
    +

    Pasting data from a content URI

    +

    + If the {@link android.content.ClipData.Item} object contains a content URI and you + have determined that you can handle one of its MIME types, create a + {@link android.content.ContentResolver} and then call the appropriate content provider + method to retrieve the data. +

    +

    + The following procedure describes how to get data from a content provider based on a + content URI on the clipboard. It checks that a MIME type that the application can use + is available from the provider: +

    +
      +
    1. + Declare a global variable to contain the MIME type: +
      +// Declares a MIME type constant to match against the MIME types offered by the provider
      +public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
      +
      +
    2. +
    3. + Get the global clipboard. Also get a content resolver so you can access the content + provider: +
      +// Gets a handle to the Clipboard Manager
      +ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
      +
      +// Gets a content resolver instance
      +ContentResolver cr = getContentResolver();
      +
      +
    4. +
    5. + Get the primary clip from the clipboard, and get its contents as a URI: +
      +// Gets the clipboard data from the clipboard
      +ClipData clip = clipboard.getPrimaryClip();
      +
      +if (clip != null) {
      +
      +    // Gets the first item from the clipboard data
      +    ClipData.Item item = clip.getItemAt(0);
      +
      +    // Tries to get the item's contents as a URI
      +    Uri pasteUri = item.getUri();
      +
      +
    6. +
    7. + Test to see if the URI is a content URI by calling + {@link android.content.ContentResolver#getType(Uri) getType(Uri)}. This method returns + null if Uri does not point to a valid content provider: +
      +    // If the clipboard contains a URI reference
      +    if (pasteUri != null) {
      +
      +        // Is this a content URI?
      +        String uriMimeType = cr.getType(pasteUri);
      +
      +
    8. +
    9. + Test to see if the content provider supports a MIME type that the current application + understands. If it does, call + {@link android.content.ContentResolver#query(Uri, String[], String, String[], String) + ContentResolver.query()} to get the data. The return value is a + {@link android.database.Cursor}: +
      +        // If the return value is not null, the Uri is a content Uri
      +        if (uriMimeType != null) {
      +
      +            // Does the content provider offer a MIME type that the current application can use?
      +            if (uriMimeType.equals(MIME_TYPE_CONTACT)) {
      +
      +                // Get the data from the content provider.
      +                Cursor pasteCursor = cr.query(uri, null, null, null, null);
      +
      +                // If the Cursor contains data, move to the first record
      +                if (pasteCursor != null) {
      +                    if (pasteCursor.moveToFirst()) {
      +
      +                    // get the data from the Cursor here. The code will vary according to the
      +                    // format of the data model.
      +                    }
      +                }
      +
      +                // close the Cursor
      +                pasteCursor.close();
      +             }
      +         }
      +     }
      +}
      +
      +
    10. +
    +

    Pasting an Intent

    +

    + To paste an Intent, first get the global clipboard. Examine the + {@link android.content.ClipData.Item} object to see if it contains an Intent. Then call + {@link android.content.ClipData.Item#getIntent()} to copy the Intent to your own storage. + The following snippet demonstrates this: +

    +
    +// Gets a handle to the Clipboard Manager
    +ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    +
    +// Checks to see if the clip item contains an Intent, by testing to see if getIntent() returns null
    +Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();
    +
    +if (pasteIntent != null) {
    +
    +    // handle the Intent
    +
    +} else {
    +
    +    // ignore the clipboard, or issue an error if your application was expecting an Intent to be
    +    // on the clipboard
    +}
    +
    +

    Using Content Providers to Copy Complex Data

    +

    + Content providers support copying complex data such as database records or file streams. + To copy the data, you put a content URI on the clipboard. Pasting applications then get this + URI from the clipboard and use it to retrieve database data or file stream descriptors. +

    +

    + Since the pasting application only has the content URI for your data, it needs to know which + piece of data to retrieve. You can provide this information by encoding an identifier for the + data on the URI itself, or you can provide a unique URI that will return the data you want to + copy. Which technique you choose depends on the organization of your data. +

    +

    + The following sections describe how to set up URIs, how to provide complex data, and how to + provide file streams. The descriptions assume that you are familiar with the general principles + of content provider design. +

    +

    Encoding an identifier on the URI

    +

    + A useful technique for copying data to the clipboard with a URI is to encode an identifier for + the data on the URI itself. Your content provider can then get the identifier from the URI and + use it to retrieve the data. The pasting application doesn't have to know that the identifier + exists; all it has to do is get your "reference" (the URI plus the identifier) from + the clipboard, give it your content provider, and get back the data. +

    +

    + You usually encode an identifier onto a content URI by concatenating it to the end of the URI. + For example, suppose you define your provider URI as the following string: +

    +
    +"content://com.example.contacts"
    +
    +

    + If you want to encode a name onto this URI, you would use the following snippet: +

    +
    +String uriString = "content://com.example.contacts" + "/" + "Smith"
    +
    +// uriString now contains content://com.example.contacts/Smith.
    +
    +// Generates a uri object from the string representation
    +Uri copyUri = Uri.parse(uriString);
    +
    +

    + If you are already using a content provider, you may want to add a new URI path that indicates + the URI is for copying. For example, suppose you already have the following URI paths: +

    +
    +"content://com.example.contacts"/people
    +"content://com.example.contacts"/people/detail
    +"content://com.example.contacts"/people/images
    +
    +

    + You could add another path that is specific to copy URIs: +

    +
    +"content://com.example.contacts/copying"
    +
    +

    + You could then detect a "copy" URI by pattern-matching and handle it with code that + is specific for copying and pasting. +

    +

    + You normally use the encoding technique if you're already using a content provider, internal + database, or internal table to organize your data. In these cases, you have multiple pieces of + data you want to copy, and presumably a unique identifier for each piece. In response to a + query from the pasting application, you can look up the data by its identifier and return it. +

    +

    + If you don't have multiple pieces of data, then you probably don't need to encode an identifier. + You can simply use a URI that is unique to your provider. In response to a query, your provider + would return the data it currently contains. +

    +

    + Getting a single record by ID is used in the + Note Pad sample application to + open a note from the notes list. The sample uses the _id field from an SQL + database, but you can have any numeric or character identifier you want. +

    +

    Copying data structures

    +

    + You set up a content provider for copying and pasting complex data as a subclass of the + {@link android.content.ContentProvider} component. You should also encode the URI you put on + the clipboard so that it points to the exact record you want to provide. In addition, you + have to consider the existing state of your application: +

    + +

    +In the content provider, you will want to override at least the following methods: +

    +
    +
    +{@link android.content.ContentResolver#query(Uri, String[], String, String[], String) query()} +
    +
    + Pasting applications will assume that they can get your data by using this method with + the URI you put on the clipboard. To support copying, you should have this method + detect URIs that contain a special "copy" path. Your application can then + create a "copy" URI to put on the clipboard, containing the copy path and + a pointer to the exact record you want to copy. +
    +
    + {@link android.content.ContentProvider#getType(Uri) getType()} +
    +
    + This method should return the MIME type or types for the data you intend to copy. The method + {@link android.content.ClipData#newUri(ContentResolver, CharSequence, Uri) newUri()} calls + {@link android.content.ContentProvider#getType(Uri) getType()} in order to put the MIME + types into the new {@link android.content.ClipData} object. +

    + MIME types for complex data are described in the topic + Content Providers. +

    +
    +
    +

    + Notice that you don't have to have any of the other content provider methods such as + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} or + {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) update()}. + A pasting application only needs to get your supported MIME types and copy data from your + provider. If you already have these methods, they won't interfere with copy operations. +

    +

    + The following snippets demonsrate how to set up your application to copy complex data: +

    +
      +
    1. +

      + In the global constants for your application, + declare a base URI string and a path that identifies URI strings you are + using to copy data. Also declare a MIME type for the copied data: +

      +
      +// Declares the base URI string
      +private static final String CONTACTS = "content://com.example.contacts";
      +
      +// Declares a path string for URIs that you use to copy data
      +private static final String COPY_PATH = "/copy";
      +
      +// Declares a MIME type for the copied data
      +public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
      +
      +
    2. +
    3. + In the Activity from which users copy data, + set up the code to copy data to the clipboard. In response to a copy request, put + the URI on the clipboard: +
      +public class MyCopyActivity extends Activity {
      +
      +    ...
      +
      +// The user has selected a name and is requesting a copy.
      +case R.id.menu_copy:
      +
      +    // Appends the last name to the base URI
      +    // The name is stored in "lastName"
      +    uriString = CONTACTS + COPY_PATH + "/" + lastName;
      +
      +    // Parses the string into a URI
      +    Uri copyUri = Uri.parse(uriString);
      +
      +    // Gets a handle to the clipboard service.
      +    ClipboardManager clipboard = (ClipboardManager)
      +        getSystemService(Context.CLIPBOARD_SERVICE);
      +
      +    ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
      +
      +    // Set the clipboard's primary clip.
      +    clipboard.setPrimaryClip(clip);
      +
      +
    4. + +
    5. +

      + In the global scope of your content provider, create a URI matcher and add a URI + pattern that will match URIs you put on the clipboard: +

      +
      +public class MyCopyProvider extends ContentProvider {
      +
      +    ...
      +
      +// A Uri Match object that simplifies matching content URIs to patterns.
      +private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
      +
      +// An integer to use in switching based on the incoming URI pattern
      +private static final int GET_SINGLE_CONTACT = 0;
      +
      +...
      +
      +// Adds a matcher for the content URI. It matches
      +// "content://com.example.contacts/copy/*"
      +sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
      +
      +
    6. +
    7. +

      + Set up the + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) query()} + method. This method can handle different URI patterns, depending on how you code it, but + only the pattern for the clipboard copying operation is shown: +

      +
      +// Sets up your provider's query() method.
      +public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
      +    String sortOrder) {
      +
      +    ...
      +
      +    // Switch based on the incoming content URI
      +    switch (sUriMatcher.match(uri)) {
      +
      +    case GET_SINGLE_CONTACT:
      +
      +        // query and return the contact for the requested name. Here you would decode
      +        // the incoming URI, query the data model based on the last name, and return the result
      +        // as a Cursor.
      +
      +    ...
      +
      +}
      +
      +
    8. +
    9. +

      + Set up the {@link android.content.ContentProvider#getType(Uri) getType()} method to + return an appropriate MIME type for copied data: +

      +
      +// Sets up your provider's getType() method.
      +public String getType(Uri uri) {
      +
      +    ...
      +
      +    switch (sUriMatcher.match(uri)) {
      +
      +    case GET_SINGLE_CONTACT:
      +
      +            return (MIME_TYPE_CONTACT);
      +
      +
    10. +
    +

    + The section Pasting data from a content URI + describes how to get a content URI from the clipboard and use it to get and paste data. +

    +

    Copying data streams

    +

    + You can copy and paste large amounts of text and binary data as streams. The data can have + forms such as the following: +

    + +

    + A content provider for data streams provides access to its data with a file descriptor object + such as {@link android.content.res.AssetFileDescriptor} instead of a + {@link android.database.Cursor} object. The pasting application reads the data stream using + this file descriptor. +

    +

    + To set up your application to copy a data stream with a provider, follow these steps: +

    +
      +
    1. + Set up a content URI for the data stream you are putting on the clipboard. Options + for doing this include the following: +
        +
      • + Encode an identifier for the data stream onto the URI, + as described in the section + Encoding an identifier on the URI, and then maintain a + table in your provider that contains identifiers and the corresponding stream name. +
      • +
      • + Encode the stream name directly on the URI. +
      • +
      • + Use a unique URI that always returns the current stream from the provider. If you + use this option, you have to remember to update your provider to point to a + different stream whenever you copy the stream to the clipboard via the URI. +
      • +
      +
    2. +
    3. + Provide a MIME type for each type of data stream you plan to offer. Pasting applications + need this information to determine if they can paste the data on the clipboard. +
    4. +
    5. + Implement one of the {@link android.content.ContentProvider} methods that returns + a file descriptor for a stream. If you encode identifiers on the content URI, use this + method to determine which stream to open. +
    6. +
    7. + To copy the data stream to the clipboard, construct the content URI and place it + on the clipboard. +
    8. +
    +

    + To paste a data stream, an application gets the clip from the clipboard, gets the URI, and + uses it in a call to a {@link android.content.ContentResolver} file descriptor method that + opens the stream. The {@link android.content.ContentResolver} method calls the corresponding + {@link android.content.ContentProvider} method, passing it the content URI. Your provider + returns the file descriptor to {@link android.content.ContentResolver} method. The pasting + application then has the responsibility to read the data from the stream. +

    +

    + The following list shows the most important file descriptor methods for a content provider. + Each of these has a corresponding {@link android.content.ContentResolver} method with the + string "Descriptor" appended to the method name; for example, the + {@link android.content.ContentResolver} analog of + {@link android.content.ContentProvider#openAssetFile(Uri, String) openAssetFile()} is +{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) openAssetFileDescriptor()}: +

    +
    +
    +{@link android.content.ContentProvider#openTypedAssetFile(Uri,String,Bundle) openTypedAssetFile()} +
    +
    + This method should return an asset file descriptor, but only if the provided MIME type is + supported by the provider. The caller (the application doing the pasting) provides a MIME + type pattern. The content provider (of the application that has copied a URI to the + clipboard) returns an {@link android.content.res.AssetFileDescriptor} file handle if it + can provide that MIME type, or throws an exception if it can not. +

    + This method handles subsections of files. You can use it to read assets that the + content provider has copied to the clipboard. +

    +
    +
    + {@link android.content.ContentProvider#openAssetFile(Uri, String) openAssetFile()} +
    +
    + This method is a more general form of +{@link android.content.ContentProvider#openTypedAssetFile(Uri,String,Bundle) openTypedAssetFile()}. + It does not filter for allowed MIME types, but it can read subsections of files. +
    +
    + {@link android.content.ContentProvider#openFile(Uri, String) openFile()} +
    +
    + This is a more general form of + {@link android.content.ContentProvider#openAssetFile(Uri, String) openAssetFile()}. It can't + read subsections of files. +
    +
    +

    + You can optionally use the +{@link android.content.ContentProvider#openPipeHelper(Uri, String, Bundle, T, ContentProvider.PipeDataWriter) openPipeHelper()} + method with your file descriptor method. This allows the pasting application to read the + stream data in a background thread using a pipe. To use this method, you need to implement the + {@link android.content.ContentProvider.PipeDataWriter} interface. An example of doing this is + given in the Note Pad sample + application, in the openTypedAssetFile() method of + NotePadProvider.java. +

    +

    Designing Effective Copy/Paste Functionality

    +

    + To design effective copy and paste functionality for your application, remember these + points: +

    + diff --git a/docs/html/guide/topics/ui/drag-drop.jd b/docs/html/guide/topics/ui/drag-drop.jd new file mode 100644 index 0000000000000..588b05b328dbf --- /dev/null +++ b/docs/html/guide/topics/ui/drag-drop.jd @@ -0,0 +1,995 @@ +page.title=Dragging and Dropping +@jd:body +
    +
    +

    Quickview

    +
      +
    • + Allow users to move data within your Activity layout using graphical gestures. +
    • +
    • + Supports operations besides data movement. +
    • +
    • + Only works within a single application. +
    • +
    • + Requires API 11. +
    • +
    +

    In this document

    +
      +
    1. + Overview +
        +
      1. + The drag/drop process +
      2. +
      3. + The drag event listener and callback method +
      4. +
      5. + Drag events +
      6. +
      7. + + The drag shadow +
      8. +
      +
    2. +
    3. + Designing a Drag and Drop Operation +
        +
      1. + Starting a drag +
      2. +
      3. + Responding to a drag start +
      4. +
      5. + Handling events during the drag +
      6. +
      7. + Responding to a drop +
      8. +
      9. + Responding to a drag end +
      10. +
      11. + Responding to drag events: an example +
      12. +
      +
    4. +
    +

    Key classes

    +
      +
    1. + {@link android.view.View View} +
    2. +
    3. + {@link android.view.View.OnLongClickListener OnLongClickListener} +
    4. +
    5. + {@link android.view.View.OnDragListener OnDragListener} +
    6. +
    7. + {@link android.view.DragEvent DragEvent} +
    8. +
    9. + {@link android.view.View.DragShadowBuilder DragShadowBuilder} +
    10. +
    11. + {@link android.content.ClipData ClipData} +
    12. +
    13. + {@link android.content.ClipDescription ClipDescription} +
    14. +
    +

    Related Samples

    +
      +
    1. + + Honeycomb-Gallery sample application. +
    2. +
    3. + +DragAndDropDemo.java and + +DraggableDot.java in Api Demos. +
    4. +
    +

    See also

    +
      +
    1. + Content Providers +
    2. +
    3. + Handling UI Events +
    4. +
    +
    +
    +

    + With the Android drag/drop framework, you can allow your users to move data + from one View to another View in the current layout using a graphical drag and drop gesture. + The framework includes a drag event class, drag listeners, and helper methods and classes. +

    +

    + Although the framework is primarily designed for data movement, you can use + it for other UI actions. For example, you could create an app that mixes colors when the user + drags a color icon over another icon. The rest of this topic, however, describes the + framework in terms of data movement. +

    +

    Overview

    +

    + A drag and drop operation starts when the user makes some gesture that you recognize as a + signal to start dragging data. In response, your application tells the system that the drag is + starting. The system calls back to your application to get a representation of the data + being dragged. As the user's finger moves this representation (a "drag shadow") + over the current layout, the system sends drag events to the drag event listener objects and + drag event callback methods associated with the {@link android.view.View} objects in the layout. + Once the user releases the drag shadow, the system ends the drag operation. +

    +

    + You create a drag event listener object ("listeners") from a class that implements + {@link android.view.View.OnDragListener}. You set the drag event listener object for a View + with the View object's + {@link android.view.View#setOnDragListener(View.OnDragListener) setOnDragListener()} method. + Each View object also has a {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} + callback method. Both of these are described in more detail in the section + The drag event listener and callback method. +

    +

    + Note: For the sake of simplicity, the following sections refer to the routine + that receives drag events as the "drag event listener", even though it may actually + be a callback method. +

    +

    + When you start a drag, you include both the data you are moving and metadata describing this + data as part of the call to the system. During the drag, the system sends drag events to the + drag event listeners or callback methods of each View in the layout. The listeners or callback + methods can use the metadata to decide if they want to accept the data when it is dropped. + If the user drops the data over a View object, and that View object's listener or callback + method has previously told the system that it wants to accept the drop, then the system sends + the data to the listener or callback method in a drag event. +

    +

    + Your application tells the system to start a drag by calling the + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} + method. This tells the system to start sending drag events. The method also sends the data that + you are dragging. +

    +

    + You can call + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} + for any attached View in the current layout. The system only uses the View object to get access + to global settings in your layout. +

    +

    + Once your application calls + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}, + the rest of the process uses events that the system sends to the View objects in your current + layout. +

    +

    The drag/drop process

    +

    + There are basically four steps or states in the drag and drop process: +

    +
    +
    + Started +
    +
    + In response to the user's gesture to begin a drag, your application calls + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} + to tell the system to start a drag. The arguments + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} + provide the data to be dragged, metadata for this data, and a callback for drawing the + drag shadow. +

    + The system first responds by calling back to your application to get a drag shadow. It + then displays the drag shadow on the device. +

    +

    + Next, the system sends a drag event with action type + {@link android.view.DragEvent#ACTION_DRAG_STARTED} to the drag event listeners for + all the View objects in the current layout. To continue to receive drag events, + including a possible drop event, a drag event listener must return true. + This registers the listener with the system. 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. +

    +

    + If the drag event listener returns false, then it will not receive drag + events for the current operation until the system sends a drag event with action type + {@link android.view.DragEvent#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 +
    +
    + The user continues the drag. As the drag shadow intersects the bounding box of a View + object, the system sends one or more drag events to the View object's drag event + listener (if it is registered to receive events). The listener may choose to + alter its View object's appearance in response to the event. For example, if the event + indicates that the drag shadow has entered the bounding box of the View + (action type {@link android.view.DragEvent#ACTION_DRAG_ENTERED}), the listener + can react by highlighting its View. +
    +
    + Dropped +
    +
    + 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 + {@link android.view.DragEvent#ACTION_DROP}. The drag event contains the data that was + passed to the system in the call to + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} + that started the operation. The listener is expected to return boolean true to + the system if code for accepting the drop succeeds. +

    + Note that this step only occurs if the user drops the drag shadow within the bounding + box of a View whose listener is registered to receive drag events. If the user releases + the drag shadow in any other situation, no {@link android.view.DragEvent#ACTION_DROP} + drag event is sent. +

    +
    +
    + Ended +
    +
    + After the user releases the drag shadow, and after the system sends out (if necessary) + a drag event with action type {@link android.view.DragEvent#ACTION_DROP}, the system sends + out a drag event with action type {@link android.view.DragEvent#ACTION_DRAG_ENDED} to + indicate that the drag operation is over. This is done regardless of where the user released + the drag shadow. The event is sent to every listener that is registered to receive drag + events, even if the listener received the {@link android.view.DragEvent#ACTION_DROP} event. +
    +
    +

    + Each of these four steps is described in more detail in the section + Designing a Drag and Drop Operation. +

    +

    The drag event listener and callback method

    +

    + A View receives drag events with either a drag event listener that implements + {@link android.view.View.OnDragListener} or with its + {@link android.view.View#onDragEvent(DragEvent)} callback method. + When the system calls the method or listener, it passes to them + a {@link android.view.DragEvent} object. +

    +

    + You will probably want to use the listener in most cases. When you design UIs, you usually + don't subclass View classes, but using the callback method forces you to do this in order to + override the method. In comparison, you can implement one listener class and then use it with + several different View objects. You can also implement it as an anonymous inline class. To + set the listener for a View object, call +{@link android.view.View#setOnDragListener(android.view.View.OnDragListener) setOnDragListener()}. +

    +

    + You can have both a listener and a callback method for View object. If this occurs, + the system first calls the listener. The system doesn't call the callback method unless the + listener returns false. +

    +

    + The combination of the {@link android.view.View#onDragEvent(DragEvent)} method and + {@link android.view.View.OnDragListener} is analogous to the combination + of the {@link android.view.View#onTouchEvent(MotionEvent) onTouchEvent()} and + {@link android.view.View.OnTouchListener} used with touch events. +

    +

    Drag events

    +

    + The system sends out a drag event in the form of a {@link android.view.DragEvent} object. The + object contains an action type that tells the listener what is happening in the drag/drop + process. The object contains other data, depending on the action type. +

    +

    + To get the action type, a listener calls {@link android.view.DragEvent#getAction()}. There + are six possible values, defined by constants in the {@link android.view.DragEvent} class. These + are listed in table 1. +

    +

    + The {@link android.view.DragEvent} object also contains the data that your application provided + to the system in the call to + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. + Some of the data is valid only for certain action types. The data that is valid for each action + type is summarized in table 2. It is also described in detail with + the event for which it is valid in the section + Designing a Drag and Drop Operation. +

    +

    + Table 1. DragEvent action types +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    getAction() valueMeaning
    {@link android.view.DragEvent#ACTION_DRAG_STARTED} + A View object's drag event listener receives this event action type just after the + application calls +{@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()} and + gets a drag shadow. +
    {@link android.view.DragEvent#ACTION_DRAG_ENTERED} + A View object's drag event listener receives this event action type when the drag shadow + has just entered the bounding box of the View. This is the first event action type the + listener receives when the drag shadow enters the bounding box. If the listener wants to + continue receiving drag events for this operation, it must return boolean + true to the system. +
    {@link android.view.DragEvent#ACTION_DRAG_LOCATION} + A View object's drag event listener receives this event action type after it receives a + {@link android.view.DragEvent#ACTION_DRAG_ENTERED} event while the drag shadow is + still within the bounding box of the View. +
    {@link android.view.DragEvent#ACTION_DRAG_EXITED} + A View object's drag event listener receives this event action type after it receives a + {@link android.view.DragEvent#ACTION_DRAG_ENTERED} and at least one + {@link android.view.DragEvent#ACTION_DRAG_LOCATION} event, and after the user has moved + the drag shadow outside the bounding box of the View. +
    {@link android.view.DragEvent#ACTION_DROP} + A View object's drag event listener receives this event action type when the user + releases the drag shadow over the View object. This action type is only sent to a View + object's listener if the listener returned boolean true in response to the + {@link android.view.DragEvent#ACTION_DRAG_STARTED} drag event. This action type is not + sent if the user releases the drag shadow on a View whose listener is not registered, + or if the user releases the drag shadow on anything that is not part of the current + layout. +

    + The listener is expected to return boolean true if it successfully + processes the drop. Otherwise, it should return false. +

    +
    {@link android.view.DragEvent#ACTION_DRAG_ENDED} + A View object's drag event listener receives this event action type + when the system is ending the drag operation. This action type is not necessarily + preceded by an {@link android.view.DragEvent#ACTION_DROP} event. If the system sent + a {@link android.view.DragEvent#ACTION_DROP}, receiving the + {@link android.view.DragEvent#ACTION_DRAG_ENDED} action type does not imply that the + drop operation succeeded. The listener must call + {@link android.view.DragEvent#getResult()} to get the value that was + returned in response to {@link android.view.DragEvent#ACTION_DROP}. If an + {@link android.view.DragEvent#ACTION_DROP} event was not sent, then + {@link android.view.DragEvent#getResult()} returns false. +
    +

    + Table 2. Valid DragEvent data by action type

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {@link android.view.DragEvent#getAction()} value{@link android.view.DragEvent#getClipDescription()} value{@link android.view.DragEvent#getLocalState()} value{@link android.view.DragEvent#getX()} value{@link android.view.DragEvent#getY()} value{@link android.view.DragEvent#getClipData()} value{@link android.view.DragEvent#getResult()} value
    {@link android.view.DragEvent#ACTION_DRAG_STARTED}XXX   
    {@link android.view.DragEvent#ACTION_DRAG_ENTERED}XXXX  
    {@link android.view.DragEvent#ACTION_DRAG_LOCATION}XXXX  
    {@link android.view.DragEvent#ACTION_DRAG_EXITED}XX    
    {@link android.view.DragEvent#ACTION_DROP}XXXXX 
    {@link android.view.DragEvent#ACTION_DRAG_ENDED}XX   X
    +

    + The {@link android.view.DragEvent#getAction()}, + {@link android.view.DragEvent#describeContents()}, + {@link android.view.DragEvent#writeToParcel(Parcel,int) writeToParcel()}, and + {@link android.view.DragEvent#toString()} methods always return valid data. +

    +

    + If a method does not contain valid data for a particular action type, it returns either + null or 0, depending on its result type. +

    +

    + The drag shadow +

    +

    + During a drag and drop operation, the system displays a image that the user drags. + For data movement, this image represents the data being dragged. For other operations, the + image represents some aspect of the drag operation. +

    +

    + The image is called a drag shadow. You create it with methods you declare for a + {@link android.view.View.DragShadowBuilder} object, and then pass it to the system when you + start a drag using + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. + As part of its response to + {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}, + the system invokes the callback methods you've defined in + {@link android.view.View.DragShadowBuilder} to obtain a drag shadow. +

    +

    + The {@link android.view.View.DragShadowBuilder} class has two constructors: +

    +
    +
    {@link android.view.View.DragShadowBuilder#View.DragShadowBuilder(View)}
    +
    + This constructor accepts any of your application's + {@link android.view.View} objects. The constructor stores the View object + in the {@link android.view.View.DragShadowBuilder} object, so during + the callback you can access it as you construct your drag shadow. + It doesn't have to be associated with the View (if any) that the user + selected to start the drag operation. +

    + If you use this constructor, you don't have to extend + {@link android.view.View.DragShadowBuilder} or override its methods. By default, + you will get a drag shadow that has the same appearance as the View you pass as an + argument, centered under the location where the user is touching the screen. +

    +
    +
    {@link android.view.View.DragShadowBuilder#View.DragShadowBuilder()}
    +
    + If you use this constructor, no View object is available in the + {@link android.view.View.DragShadowBuilder} object (the field is set to null). + If you use this constructor, and you don't extend + {@link android.view.View.DragShadowBuilder} or override its methods, + you will get an invisible drag shadow. + The system does not give an error. +
    +
    +

    + The {@link android.view.View.DragShadowBuilder} class has two methods: +

    +
    +
    +{@link android.view.View.DragShadowBuilder#onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} +
    +
    + The system calls this method immediately after you call +{@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}. Use it + to send to the system the dimensions and touch point of the drag shadow. The method has two + arguments: +
    +
    dimensions
    +
    + A {@link android.graphics.Point} object. The drag shadow width goes in + {@link android.graphics.Point#x} and its height goes in + {@link android.graphics.Point#y}. +
    +
    touch_point
    +
    + A {@link android.graphics.Point} object. The touch point is the location within the + drag shadow that should be under the user's finger during the drag. Its X + position goes in {@link android.graphics.Point#x} and its Y position goes in + {@link android.graphics.Point#y} +
    +
    +
    +
    + {@link android.view.View.DragShadowBuilder#onDrawShadow(Canvas) onDrawShadow()} +
    +
    + Immediately after the call to +{@link android.view.View.DragShadowBuilder#onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} + the system calls + {@link android.view.View.DragShadowBuilder#onDrawShadow(Canvas) onDrawShadow()} to get the + drag shadow itself. The method has a single argument, a {@link android.graphics.Canvas} + object that the system constructs from the parameters you provide in +{@link android.view.View.DragShadowBuilder#onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} + Use it to draw the drag shadow in the provided {@link android.graphics.Canvas} object. +
    +
    +

    + To improve performance, you should keep the size of the drag shadow small. For a single item, + you may want to use a icon. For a multiple selection, you may want to use icons in a stack + rather than full images spread out over the screen. +

    +

    Designing a Drag and Drop Operation

    +

    + This section shows step-by-step how to start a drag, 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

    +

    + The user starts a drag with a drag gesture, usually a long press, on a View object. + In response, you should do the following: +

    +
      +
    1. + As necessary, create a {@link android.content.ClipData} and + {@link android.content.ClipData.Item} for the data being moved. As part of the + ClipData object, supply metadata that is stored in a {@link android.content.ClipDescription} + object within the ClipData. For a drag and drop operation that does not represent data + movement, you may want to use null instead of an actual object. +

      + For example, this code snippet shows how to respond to a long press on a ImageView + by creating a ClipData object that contains the tag or label of an + ImageView. Following this snippet, the next snippet shows how to override the methods in + {@link android.view.View.DragShadowBuilder}: +

      +
      +// Create a string for the ImageView label
      +private static final String IMAGEVIEW_TAG = "icon bitmap"
      +
      +// Creates a new ImageView
      +ImageView imageView = new ImageView(this);
      +
      +// Sets the bitmap for the ImageView from an icon bit map (defined elsewhere)
      +imageView.setImageBitmap(mIconBitmap);
      +
      +// Sets the tag
      +imageView.setTag(IMAGEVIEW_TAG);
      +
      +    ...
      +
      +// Sets a long click listener for the ImageView using an anonymous listener object that
      +// implements the OnLongClickListener interface
      +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) {
      +
      +    // Create a new ClipData.
      +    // This is done in two steps to provide clarity. The convenience method
      +    // ClipData.newPlainText() can create a plain text ClipData in one step.
      +
      +    // Create a new ClipData.Item from the ImageView object's tag
      +    ClipData.Item item = new ClipData.Item(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"
      +    ClipData dragData = new ClipData(v.getTag(),ClipData.MIMETYPE_TEXT_PLAIN,item);
      +
      +    // Instantiates the drag shadow builder.
      +    View.DrawShadowBuilder myShadow = new MyDragShadowBuilder(imageView);
      +
      +    // Starts the drag
      +
      +            v.startDrag(dragData,  // the data to be dragged
      +                        myShadow,  // the drag shadow builder
      +                        null,      // no need to use local data
      +                        0          // flags (not currently used, set to 0)
      +            );
      +
      +    }
      +}
      +
      +
    2. +
    3. + The following code snippet defines {@code myDragShadowBuilder} + It creates a drag shadow for dragging a TextView as a small gray rectangle: +
      +    private static class MyDragShadowBuilder extends View.DragShadowBuilder {
      +
      +    // The drag shadow image, defined as a drawable thing
      +    private static Drawable shadow;
      +
      +        // Defines the constructor for myDragShadowBuilder
      +        public MyDragShadowBuilder(View v) {
      +
      +            // Stores the View parameter passed to myDragShadowBuilder.
      +            super(v);
      +
      +            // Creates a draggable image that will fill the Canvas provided by the system.
      +            shadow = new ColorDrawable(Color.LTGRAY);
      +        }
      +
      +        // Defines a callback that sends the drag shadow dimensions and touch point back to the
      +        // system.
      +        @Override
      +        public void onProvideShadowMetrics (Point size, Point touch)
      +            // Defines local variables
      +            private int width, height;
      +
      +            // Sets the width of the shadow to half the width of the original View
      +            width = getView().getWidth() / 2;
      +
      +            // Sets the height of the shadow to half the height of the original View
      +            height = getView().getHeight() / 2;
      +
      +            // The drag shadow is a ColorDrawable. This sets its dimensions to be the same as the
      +            // Canvas that the system will provide. As a result, the drag shadow will fill the
      +            // Canvas.
      +            shadow.setBounds(0, 0, width, height);
      +
      +            // Sets the size parameter's width and height values. These get back to the system
      +            // through the size parameter.
      +            size.set(width, height);
      +
      +            // Sets the touch point's position to be in the middle of the drag shadow
      +            touch.set(width / 2, height / 2);
      +        }
      +
      +        // Defines a callback that draws the drag shadow in a Canvas that the system constructs
      +        // from the dimensions passed in onProvideShadowMetrics().
      +        @Override
      +        public void onDrawShadow(Canvas canvas) {
      +
      +            // Draws the ColorDrawable in the Canvas passed in from the system.
      +            shadow.draw(canvas);
      +        }
      +    }
      +
      +

      + Note: Remember that you don't have to extend + {@link android.view.View.DragShadowBuilder}. The constructor + {@link android.view.View.DragShadowBuilder#View.DragShadowBuilder(View)} creates a + default drag shadow that's the same size as the View argument passed to it, with the + touch point centered in the drag shadow. +

      +
    4. +
    +

    Responding to a drag start

    +

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

    +

    + In response to an event with the action type {@link android.view.DragEvent#ACTION_DRAG_STARTED}, + a listener should do the following: +

    +
      +
    1. + Call {@link android.view.DragEvent#getClipDescription()} to get the + {@link android.content.ClipDescription}. Use the MIME type methods in + {@link android.content.ClipDescription} to see if the listener can accept the data being + dragged. +

      + If the drag and drop operation does not represent data movement, this may not be + necessary. +

      +
    2. +
    3. + 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 + {@link android.view.DragEvent#ACTION_DRAG_ENDED}. +
    4. +
    +

    + Note that for an {@link android.view.DragEvent#ACTION_DRAG_STARTED} event, these + the following {@link android.view.DragEvent} methods are not valid: + {@link android.view.DragEvent#getClipData()}, {@link android.view.DragEvent#getX()}, + {@link android.view.DragEvent#getY()}, and {@link android.view.DragEvent#getResult()}. +

    +

    Handling events during the drag

    +

    + During the drag, listeners that returned true in response to + the {@link android.view.DragEvent#ACTION_DRAG_STARTED} drag event continue to receive drag + events. The types of drag events a listener receives during the drag depend on the location of + the drag shadow and the visibility of the listener's View. +

    +

    + During the drag, listeners primarily use drag events to decide if they should change the + appearance of their View. +

    +

    + During the drag, {@link android.view.DragEvent#getAction()} returns one of three + values: +

    + +

    + The listener does not need to react to any of these action types. If the listener returns a + value to the system, it is ignored. Here are some guidelines for responding to each of + these action types: +

    + +

    Responding to a drop

    +

    + When the user releases the drag shadow on a View in the application, and that View previously + reported that it could accept the content being dragged, the system dispatches a drag event + to that View with the action type {@link android.view.DragEvent#ACTION_DROP}. The listener + should do the following: +

    +
      +
    1. + Call {@link android.view.DragEvent#getClipData()} to get the + {@link android.content.ClipData} object that was originally supplied in the call + to +{@link android.view.View#startDrag(ClipData, View.DragShadowBuilder, Object, int) startDrag()} + and store it. If the drag and drop operation does not represent data movement, + this may not be necessary. +
    2. +
    3. + Return boolean true to indicate that the drop was processed successfully, or + boolean false if it was not. The returned value becomes the value returned by + {@link android.view.DragEvent#getResult()} for an + {@link android.view.DragEvent#ACTION_DRAG_ENDED} event. +

      + Note that if the system does not send out an {@link android.view.DragEvent#ACTION_DROP} + event, the value of {@link android.view.DragEvent#getResult()} for an + {@link android.view.DragEvent#ACTION_DRAG_ENDED} event is false. +

      +
    4. +
    +

    + For an {@link android.view.DragEvent#ACTION_DROP} event, + {@link android.view.DragEvent#getX()} and {@link android.view.DragEvent#getY()} + return the X and Y position of the drag point at the moment of the drop, using the coordinate + system of the View that received the drop. +

    +

    + The system does allow the user to release the drag shadow on a View whose listener is not + receiving drag events. It will also allow the user to release the drag shadow + on empty regions of the application's UI, or on areas outside of your application. + In all of these cases, the system does not send an event with action type + {@link android.view.DragEvent#ACTION_DROP}, although it does send out an + {@link android.view.DragEvent#ACTION_DRAG_ENDED} event. +

    +

    Responding to a drag end

    +

    + Immediately after the user releases the drag shadow, the system sends a + drag event to all of the drag event listeners in your application, with an action type of + {@link android.view.DragEvent#ACTION_DRAG_ENDED}. This indicates that the drag operation is + over. +

    +

    + Each listener should do the following: +

    +
      +
    1. + If listener changed its View object's appearance during the operation, it should reset the + View to its default appearance. This is a visual indication to the user that the operation + is over. +
    2. +
    3. + The listener can optionally call {@link android.view.DragEvent#getResult()} to find out more + about the operation. If a listener returned true in response to an event of + action type {@link android.view.DragEvent#ACTION_DROP}, then + {@link android.view.DragEvent#getResult()} will return boolean true. In all + other cases, {@link android.view.DragEvent#getResult()} returns boolean false, + including any case in which the system did not send out a + {@link android.view.DragEvent#ACTION_DROP} event. +
    4. +
    5. + The listener should return boolean true to the system. +
    6. +
    +

    +

    +

    Responding to drag events: an example

    +

    + All drag events are initially received by your drag event method or listener. The following + code snippet is a simple example of reacting to drag events in a listener: +

    +
    +// Creates a new drag event listener
    +mDragListen = new myDragEventListener();
    +
    +View imageView = new ImageView(this);
    +
    +// Sets the drag event listener for the View
    +imageView.setOnDragListener(mDragListen);
    +
    +...
    +
    +protected class myDragEventListener implements View.OnDragEventListener {
    +
    +    // This is the method that the system calls when it dispatches a drag event to the
    +    // listener.
    +    public boolean onDrag(View v, DragEvent event) {
    +
    +        // Defines a variable to store the action type for the incoming event
    +        final 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)) {
    +
    +                    // As an example of what your application might do,
    +                    // applies a blue color tint to the View to indicate that it can accept
    +                    // data.
    +                    v.setColorFilter(Color.BLUE);
    +
    +                    // 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);
    +
    +                    } else {
    +
    +                    // Returns false. During the current drag and drop operation, this View will
    +                    // not receive events again until ACTION_DRAG_ENDED is sent.
    +                    return(false);
    +
    +                    }
    +                break;
    +
    +            case DragEvent.ACTION_DRAG_ENTERED: {
    +
    +                // Applies a green tint to the View. Return true; the return value is ignored.
    +
    +                v.setColorFilter(Color.GREEN);
    +
    +                // Invalidate the view to force a redraw in the new tint
    +                v.invalidate();
    +
    +                return(true);
    +
    +                break;
    +
    +                case DragEvent.ACTION_DRAG_LOCATION:
    +
    +                // Ignore the event
    +                    return(true);
    +
    +                break;
    +
    +                case DragEvent.ACTION_DRAG_EXITED:
    +
    +                    // Re-sets the color tint to blue. Returns true; the return value is ignored.
    +                    v.setColorFilter(Color.BLUE);
    +
    +                    // Invalidate the view to force a redraw in the new tint
    +                    v.invalidate();
    +
    +                    return(true);
    +
    +                break;
    +
    +                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.
    +                    dragData = item.getText();
    +
    +                    // Displays a message containing the dragged data.
    +                    Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG);
    +
    +                    // Turns off any color tints
    +                    v.clearColorFilter();
    +
    +                    // Invalidates the view to force a redraw
    +                    v.invalidate();
    +
    +                    // Returns true. DragEvent.getResult() will return true.
    +                    return(true);
    +
    +                break;
    +
    +                case DragEvent.ACTION_DRAG_ENDED:
    +
    +                    // Turns off any color tinting
    +                    v.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_LONG);
    +
    +                    } else {
    +                        Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG);
    +
    +                    };
    +
    +                    // returns true; the value is ignored.
    +                    return(true);
    +
    +                break;
    +
    +                // An unknown action type was received.
    +                default:
    +                    Log.e("DragDrop Example","Unknown action type received by OnDragListener.");
    +
    +                break;
    +        };
    +    };
    +};
    +
    \ No newline at end of file diff --git a/docs/html/images/ui/clipboard/copy_paste_framework.png b/docs/html/images/ui/clipboard/copy_paste_framework.png new file mode 100755 index 0000000000000000000000000000000000000000..57facaab9018219886d2b62ee462d00a55ea5503 GIT binary patch literal 37996 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 Hl+XkK3?(r( literal 0 HcmV?d00001