Stosowanie przeciągania i upuszczania w widokach

Proces przeciągania i upuszczania możesz zaimplementować w widokach, odpowiadając na wydarzenia które mogą powodować przeciąganie, reagowanie i zużywanie zdarzeń upuszczania.

Rozpocznij przeciąganie

Użytkownik rozpoczyna przeciąganie gestem, zwykle przez dotknięcie lub kliknięcie i przytrzymuje element, który chcesz przeciągnąć.

Aby obsłużyć tę funkcję w View, utwórz ClipData i Obiekt ClipData.Item dla przenoszone dane. W ramach zasady ClipData prześlij metadane, które są przechowywane w ClipDescription obiekt w: ClipData. W przypadku operacji „przeciągnij i upuść”, która nie odzwierciedla przenoszenia danych, lepiej użyć obiektu null zamiast rzeczywistego obiektu.

Na przykład ten fragment kodu pokazuje, jak zareagować na kliknięcie & przytrzymaj aby wykonać gest na ImageView, tworząc obiekt ClipData zawierający tag (lub etykieta) elementu ImageView:

Kotlin

// Create a string for the ImageView label.
val IMAGEVIEW_TAG = "icon bitmap"
...
val imageView = ImageView(context).apply {
    // Set the bitmap for the ImageView from an icon bitmap defined elsewhere.
    setImageBitmap(iconBitmap)
    tag = IMAGEVIEW_TAG
    setOnLongClickListener { 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.
        val item = ClipData.Item(v.tag as? CharSequence)

        // Create a new ClipData using the tag as a label, the plain text
        // MIME type, and the already-created item. This creates a new
        // ClipDescription object within the ClipData and sets its MIME type
        // to "text/plain".
        val dragData = ClipData(
            v.tag as? CharSequence,
            arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
            item)

        // Instantiate the drag shadow builder. We use this imageView object
        // to create the default builder.
        val myShadow = View.DragShadowBuilder(view: this)

        // Start the drag.
        v.startDragAndDrop(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.
        )

        // Indicate that the long-click is handled.
        true
    }
}

Java

// Create a string for the ImageView label.
private static final String IMAGEVIEW_TAG = "icon bitmap";
...
// Create a new ImageView.
ImageView imageView = new ImageView(context);

// Set the bitmap for the ImageView from an icon bitmap defined elsewhere.
imageView.setImageBitmap(iconBitmap);

// Set the tag.
imageView.setTag(IMAGEVIEW_TAG);

// Set a long-click listener for the ImageView using an anonymous listener
// object that implements the OnLongClickListener interface.
imageView.setOnLongClickListener( 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((CharSequence) v.getTag());

    // Create a new ClipData using the tag as a label, the plain text MIME type,
    // and the already-created item. This creates a new ClipDescription object
    // within the ClipData and sets its MIME type to "text/plain".
    ClipData dragData = new ClipData(
            (CharSequence) v.getTag(),
            new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN },
            item);

    // Instantiate the drag shadow builder. We use this imageView object
    // to create the default builder.
    View.DragShadowBuilder myShadow = new View.DragShadowBuilder(imageView);

    // Start the drag.
    v.startDragAndDrop(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.
    );

    // Indicate that the long-click is handled.
    return true;
});

Reaguj na rozpoczęcie przeciągania

Podczas operacji przeciągania system wysyła zdarzenia przeciągania do zdarzenia przeciągania detektory obiektów View w bieżącym układzie. Reakcja słuchaczy wywołaj DragEvent.getAction(), aby uzyskać typ działania. Na początku przeciągania ta metoda zwraca ACTION_DRAG_STARTED.

W odpowiedzi na zdarzenie o typie działania ACTION_DRAG_STARTED – zdarzenie przeciągania detektor musi wykonać te czynności:

  1. Zadzwoń do nas DragEvent.getClipDescription() i użyj metod typu MIME w zwróconym obiekcie ClipDescription, aby sprawdzić, czy odbiornik może zaakceptować przeciągane dane.

    Jeśli operacja „przeciągnij i upuść” nie odzwierciedla przenoszenia danych, może to spowodować być zbędne.

  2. Jeśli detektor zdarzeń przeciągania może zaakceptować upuszczanie, musi zwrócić wartość true, aby poinformować system, aby nadal wysyłał zdarzenia przeciągania do detektora. Jeśli detektor nie może zaakceptować upadku, detektor musi zwrócić wartość false, a system zatrzyma wysyła zdarzenia przeciągania do detektora, dopóki system nie wyśle ACTION_DRAG_ENDED, aby zakończyć operację przeciągania i upuszczania.

W przypadku zdarzenia ACTION_DRAG_STARTED te metody DragEvent nie są prawidłowe: getClipData(), getX(), getY() i getResult().

Obsługa zdarzeń podczas przeciągania

Podczas przeciągania przeciągaj detektory zdarzeń, które zwracają parametr true w odpowiedzi na zdarzenia przeciągania ACTION_DRAG_STARTED nadal będą otrzymywać zdarzenia przeciągania. Typy zdarzeń przeciągania, które detektor odbiera podczas przeciągania, zależy od lokalizacji elementu przeciągnąć cień i widoczność elementu View słuchacza. Słuchacze używają „przeciągnij”, wydarzeń, głównie wtedy, gdy decydują, czy muszą zmienić wygląd panelu View.

Podczas przeciągania funkcja DragEvent.getAction() zwraca jedną z 3 wartości:

  • ACTION_DRAG_ENTERED: detektor odbiera ten typ działania zdarzenia, gdy punkt styczności z klientem na ekranie pod palcem lub myszą użytkownika – powoduje ramki ograniczającej elementu View detektora.
  • ACTION_DRAG_LOCATION: gdy detektor odbierze zdarzenie ACTION_DRAG_ENTERED, otrzyma nowe ACTION_DRAG_LOCATION zdarzenie za każdym razem, gdy punkt kontaktu przesuwa się do niego odbiera zdarzenie ACTION_DRAG_EXITED. Metody getX() i getY() zwraca współrzędne X i Y punktu styku.
  • ACTION_DRAG_EXITED: ten typ działania zdarzenia jest wysyłany do detektora, który wcześniej odbierał ACTION_DRAG_ENTERED Zdarzenie jest wysyłane, gdy punkt kontaktu cienia przeciągania przechodzi z ramki ograniczającej w View detektora do poza ramkę ograniczającą.

Detektor zdarzeń przeciągania nie musi reagować na żaden z tych typów działań. Jeśli detektor zwraca wartość do systemu, jest ona ignorowana.

Oto kilka wskazówek dotyczących reagowania na każdy z tych rodzajów działań:

  • W odpowiedzi na polecenie ACTION_DRAG_ENTERED lub ACTION_DRAG_LOCATION detektor może zmienić wygląd View, by wskazać, że jest to potencjalny spadek wartości docelowej.
  • Zdarzenie o typie działania ACTION_DRAG_LOCATION zawiera prawidłowe dane dla getX() i getY() odpowiadające lokalizacji punktu styku. detektor może wykorzystać te informacje, aby zmienić wygląd View w lub w celu określenia dokładnego miejsca, w którym użytkownik może upuścić treści.
  • W odpowiedzi na żądanie ACTION_DRAG_EXITED detektor musi zresetować każdy wygląd zmiany, jakie ma zastosować w odpowiedzi na warunki ACTION_DRAG_ENTERED lub ACTION_DRAG_LOCATION Informuje to użytkownika, że element View nie jest adresem i dalszym celem.

Odpowiedz na spadek

Gdy użytkownik zwolni cień na obiekt View, a wcześniej View raportuje, że może przyjmować przeciąganą treść, a system wysyła zdarzenie przeciągnięcia do komponentu View o typie działania ACTION_DROP.

Detektor zdarzeń przeciągania musi:

  1. Wywołaj getClipData(), aby uzyskać obiekt ClipData, który został pierwotnie podane w wywołaniu do startDragAndDrop(). i przetwarzaj dane. Jeśli funkcja „przeciągnij i upuść” nie pokazuje danych ruchu, nie jest to konieczne.

  2. Zwraca wartość logiczną true, która wskazuje, że spadek został przetworzony. lub false, jeśli nie jest. Zwrócona wartość staje się wartością zwracaną przez funkcję getResult() w przypadku końcowego zdarzenia ACTION_DRAG_ENDED. Jeśli system nie wysyła zdarzenia ACTION_DROP, wartość zwrócona przez funkcję getResult() dla wydarzenia ACTION_DRAG_ENDED to false.

W przypadku wydarzenia ACTION_DROP getX() i getY() używają układu współrzędnych View, który otrzymuje spadek, aby zwrócić pozycję X i Y dla i punktu styczności z klientem w momencie spadku.

Użytkownik może zwolnić cień obiektu View, którego zdarzenie przeciągania jest możliwe. detektor nie odbiera zdarzeń przeciągania, pustych obszarów UI aplikacji, a nawet poza obszarem aplikacji, Android nie wyśle zdarzenia z działaniem wpisz ACTION_DROP i będzie wysyłać tylko zdarzenie ACTION_DRAG_ENDED.

Reagują na koniec przeciągania

Natychmiast po zwolnieniu cienia przez użytkownika system wysyła zdarzenie z typem działania ACTION_DRAG_ENDED do wszystkich detektorów zdarzeń przeciągania w Twojej aplikacji. Oznacza to, że przeciąganie zostało zakończone.

Każdy detektor zdarzeń przeciągania musi wykonać te czynności:

  1. Jeśli podczas operacji detektor zmieni swój wygląd, powinien się zresetować przywrócić domyślny wygląd, aby w sposób wizualny wspomóc użytkownika, .
  2. Słuchacz może opcjonalnie wywołać funkcję getResult(), aby dowiedzieć się więcej na temat . Jeśli odbiornik zwraca wartość true w odpowiedzi na zdarzenie działania wpisz ACTION_DROP, a getResult() zwraca wartość logiczną true. We wszystkich pozostałych przypadków getResult() zwraca wartość logiczną false, również wtedy, gdy system nie wysyła zdarzenia ACTION_DROP.
  3. Aby wskazać, że operacja usuwania zakończyła się powodzeniem, odbiornik powinna zwrócić do systemu wartość logiczną true. Jeśli nie zwrócisz wartości false, sygnalizacja świetlna wskazująca, że cień powraca do źródła, może sugerować użytkownik zobaczy, że operacja się nie powiodła.

Reagowanie na zdarzenia przeciągania: przykład

Wszystkie zdarzenia przeciągania są odbierane przez metodę lub detektor zdarzeń przeciągania. Ten fragment kodu to przykład reakcji na zdarzenia przeciągania:

Kotlin

val imageView = ImageView(this)

// Set the drag event listener for the View.
imageView.setOnDragListener { v, e ->

    // Handle each of the expected events.
    when (e.action) {
        DragEvent.ACTION_DRAG_STARTED -> {
            // Determine whether this View can accept the dragged data.
            if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                // As an example, apply a blue color tint to the View to
                // indicate that it can accept data.
                (v as? ImageView)?.setColorFilter(Color.BLUE)

                // Invalidate the view to force a redraw in the new tint.
                v.invalidate()

                // Return true to indicate that the View can accept the dragged
                // data.
                true
            } else {
                // Return false to indicate that, during the current drag and
                // drop operation, this View doesn't receive events again until
                // ACTION_DRAG_ENDED is sent.
                false
            }
        }
        DragEvent.ACTION_DRAG_ENTERED -> {
            // Apply a green tint to the View.
            (v as? ImageView)?.setColorFilter(Color.GREEN)

            // Invalidate the view to force a redraw in the new tint.
            v.invalidate()

            // Return true. The value is ignored.
            true
        }

        DragEvent.ACTION_DRAG_LOCATION ->
            // Ignore the event.
            true
        DragEvent.ACTION_DRAG_EXITED -> {
            // Reset the color tint to blue.
            (v as? ImageView)?.setColorFilter(Color.BLUE)

            // Invalidate the view to force a redraw in the new tint.
            v.invalidate()

            // Return true. The value is ignored.
            true
        }
        DragEvent.ACTION_DROP -> {
            // Get the item containing the dragged data.
            val item: ClipData.Item = e.clipData.getItemAt(0)

            // Get the text data from the item.
            val dragData = item.text

            // Display a message containing the dragged data.
            Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show()

            // Turn off color tints.
            (v as? ImageView)?.clearColorFilter()

            // Invalidate the view to force a redraw.
            v.invalidate()

            // Return true. DragEvent.getResult() returns true.
            true
        }

        DragEvent.ACTION_DRAG_ENDED -> {
            // Turn off color tinting.
            (v as? ImageView)?.clearColorFilter()

            // Invalidate the view to force a redraw.
            v.invalidate()

            // Do a getResult() and display what happens.
            when(e.result) {
                true ->
                    Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG)
                else ->
                    Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG)
            }.show()

            // Return true. The value is ignored.
            true
        }
        else -> {
            // An unknown action type is received.
            Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
            false
        }
    }
}

Java

View imageView = new ImageView(this);

// Set the drag event listener for the View.
imageView.setOnDragListener( (v, e) -> {

    // Handle each of the expected events.
    switch(e.getAction()) {

        case DragEvent.ACTION_DRAG_STARTED:

            // Determine whether this View can accept the dragged data.
            if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {

                // As an example, apply a blue color tint to the View to
                // indicate that it can accept data.
                ((ImageView)v).setColorFilter(Color.BLUE);

                // Invalidate the view to force a redraw in the new tint.
                v.invalidate();

                // Return true to indicate that the View can accept the dragged
                // data.
                return true;

            }

            // Return false to indicate that, during the current drag-and-drop
            // operation, this View doesn't receive events again until
            // ACTION_DRAG_ENDED is sent.
            return false;

        case DragEvent.ACTION_DRAG_ENTERED:

            // Apply a green tint to the View.
            ((ImageView)v).setColorFilter(Color.GREEN);

            // Invalidate the view to force a redraw in the new tint.
            v.invalidate();

            // Return true. The value is ignored.
            return true;

        case DragEvent.ACTION_DRAG_LOCATION:

            // Ignore the event.
            return true;

        case DragEvent.ACTION_DRAG_EXITED:

            // Reset the color tint to blue.
            ((ImageView)v).setColorFilter(Color.BLUE);

            // Invalidate the view to force a redraw in the new tint.
            v.invalidate();

            // Return true. The value is ignored.
            return true;

        case DragEvent.ACTION_DROP:

            // Get the item containing the dragged data.
            ClipData.Item item = e.getClipData().getItemAt(0);

            // Get the text data from the item.
            CharSequence dragData = item.getText();

            // Display a message containing the dragged data.
            Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show();

            // Turn off color tints.
            ((ImageView)v).clearColorFilter();

            // Invalidate the view to force a redraw.
            v.invalidate();

            // Return true. DragEvent.getResult() returns true.
            return true;

        case DragEvent.ACTION_DRAG_ENDED:

            // Turn off color tinting.
            ((ImageView)v).clearColorFilter();

            // Invalidate the view to force a redraw.
            v.invalidate();

            // Do a getResult() and displays what happens.
            if (e.getResult()) {
                Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show();
            }

            // Return true. The value is ignored.
            return true;

        // An unknown action type is received.
        default:
            Log.e("DragDrop Example","Unknown action type received by View.OnDragListener.");
            break;
    }

    return false;

});

Dostosowywanie cienia do przeciągania

Możesz zdefiniować niestandardową wartość myDragShadowBuilder, zastępując metody w View.DragShadowBuilder Ten fragment kodu tworzy mały, prostokątny, szary, przeciągnij cień dla obiektu TextView:

Kotlin

private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) {

    private val shadow = ColorDrawable(Color.LTGRAY)

    // Define a callback that sends the drag shadow dimensions and touch point
    // back to the system.
    override fun onProvideShadowMetrics(size: Point, touch: Point) {

            // Set the width of the shadow to half the width of the original
            // View.
            val width: Int = view.width / 2

            // Set the height of the shadow to half the height of the original
            // View.
            val height: Int = view.height / 2

            // The drag shadow is a ColorDrawable. Set its dimensions to
            // be the same as the Canvas that the system provides. As a result,
            // the drag shadow fills the Canvas.
            shadow.setBounds(0, 0, width, height)

            // Set the size parameter's width and height values. These get back
            // to the system through the size parameter.
            size.set(width, height)

            // Set the touch point's position to be in the middle of the drag
            // shadow.
            touch.set(width / 2, height / 2)
    }

    // Define a callback that draws the drag shadow in a Canvas that the system
    // constructs from the dimensions passed to onProvideShadowMetrics().
    override fun onDrawShadow(canvas: Canvas) {

            // Draw the ColorDrawable on the Canvas passed in from the system.
            shadow.draw(canvas)
    }
}

Java

private static class MyDragShadowBuilder extends View.DragShadowBuilder {

    // The drag shadow image, defined as a drawable object.
    private static Drawable shadow;

    // Constructor.
    public MyDragShadowBuilder(View view) {

            // Store the View parameter.
            super(view);

            // Create a draggable image that fills the Canvas provided by the
            // system.
            shadow = new ColorDrawable(Color.LTGRAY);
    }

    // Define a callback that sends the drag shadow dimensions and touch point
    // back to the system.
    @Override
    public void onProvideShadowMetrics (Point size, Point touch) {

            // Define local variables.
            int width, height;

            // Set the width of the shadow to half the width of the original
            // View.
            width = getView().getWidth() / 2;

            // Set the height of the shadow to half the height of the original
            // View.
            height = getView().getHeight() / 2;

            // The drag shadow is a ColorDrawable. Set its dimensions to
            // be the same as the Canvas that the system provides. As a result,
            // the drag shadow fills the Canvas.
            shadow.setBounds(0, 0, width, height);

            // Set the size parameter's width and height values. These get back
            // to the system through the size parameter.
            size.set(width, height);

            // Set the touch point's position to be in the middle of the drag
            // shadow.
            touch.set(width / 2, height / 2);
    }

    // Define a callback that draws the drag shadow in a Canvas that the system
    // constructs from the dimensions passed to onProvideShadowMetrics().
    @Override
    public void onDrawShadow(Canvas canvas) {

            // Draw the ColorDrawable on the Canvas passed in from the system.
            shadow.draw(canvas);
    }
}