Mengimplementasikan tarik lalu lepas dengan tampilan

Anda dapat menerapkan proses tarik lalu lepas dalam tampilan dengan merespons peristiwa yang mungkin memicu awal tarik, serta merespons peristiwa lepas.

Mulai operasi tarik

Pengguna memulai tarik dengan gestur, biasanya dengan menyentuh atau mengklik lama item yang ingin ditarik.

Untuk menangani hal ini di View, buat objek ClipData dan objek ClipData.Item untuk data yang sedang dipindahkan. Sebagai bagian dari ClipData, berikan metadata yang disimpan dalam objek ClipDescription dalam ClipData. Untuk operasi tarik lalu lepas yang tidak merepresentasikan perpindahan data, Anda dapat menggunakan null, bukan objek yang sebenarnya.

Misalnya, cuplikan kode ini menunjukkan cara merespons gestur sentuh lama di ImageView dengan membuat objek ClipData yang berisi tag (atau label) 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;
});

Merespons awal operasi tarik

Selama operasi tarik, sistem mengirim peristiwa tarik ke pemroses peristiwa tarik untuk objek View dalam tata letak saat ini. Pemroses akan bereaksi dengan memanggil DragEvent.getAction() untuk mendapatkan jenis tindakan. Di awal penarikan, metode ini akan menampilkan ACTION_DRAG_STARTED.

Untuk merespons peristiwa dengan jenis tindakan ACTION_DRAG_STARTED, pemroses peristiwa tarik harus melakukan hal berikut:

  1. Panggil DragEvent.getClipDescription() dan gunakan metode jenis MIME dalam ClipDescription yang ditampilkan untuk melihat apakah pemroses dapat menerima data yang sedang ditarik.

    Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini mungkin tidak diperlukan.

  2. Jika pemroses peristiwa tarik dapat menerima peristiwa lepas, pemroses harus menampilkan true untuk memberi tahu sistem agar terus mengirim peristiwa tarik ke pemroses. Jika pemroses tidak dapat menerima peristiwa lepas, pemroses harus menampilkan false, dan sistem akan berhenti mengirimkan peristiwa tarik ke pemroses hingga sistem mengirimkan ACTION_DRAG_ENDED untuk mengakhiri operasi tarik lalu lepas.

Untuk peristiwa ACTION_DRAG_STARTED, metode DragEvent berikut tidak valid: getClipData(), getX(), getY(), dan getResult().

Menangani peristiwa selama operasi tarik

Selama tindakan tarik, pemroses peristiwa tarik yang menampilkan true sebagai respons terhadap peristiwa tarik ACTION_DRAG_STARTED akan terus menerima peristiwa tarik. Jenis peristiwa tarik yang diterima pemroses selama operasi tarik bergantung pada lokasi bayangan tarik dan visibilitas View pemroses. Pemroses menggunakan peristiwa tarik terutama untuk memutuskan apakah harus mengubah tampilan View-nya.

Selama operasi tarik, DragEvent.getAction() menampilkan satu dari tiga nilai berikut:

  • ACTION_DRAG_ENTERED: pemroses menerima jenis tindakan peristiwa ini saat titik sentuh—titik pada layar di bawah jari atau mouse pengguna—memasuki kotak pembatas View pemroses.
  • ACTION_DRAG_LOCATION: setelah menerima peristiwa ACTION_DRAG_ENTERED, pemroses akan menerima peristiwa ACTION_DRAG_LOCATION baru setiap kali titik sentuh bergerak hingga menerima peristiwa ACTION_DRAG_EXITED. Metode getX() dan getY() menampilkan koordinat X dan Y titik sentuh tersebut.
  • ACTION_DRAG_EXITED: jenis tindakan peristiwa ini dikirim ke pemroses yang sebelumnya menerima ACTION_DRAG_ENTERED. Peristiwa ini dikirim saat titik sentuh bayangan tarik berpindah dari dalam kotak pembatas View pemroses ke luar kotak pembatas.

Pemroses peristiwa tarik tidak perlu bereaksi terhadap salah satu jenis tindakan ini. Jika pemroses menampilkan nilai ke sistem, nilai tersebut akan diabaikan.

Inilah beberapa panduan untuk merespons setiap tipe aksi ini:

  • Sebagai respons terhadap ACTION_DRAG_ENTERED atau ACTION_DRAG_LOCATION, pemroses dapat mengubah tampilan View untuk menunjukkan bahwa tampilan adalah potensi target lepas.
  • Peristiwa dengan jenis tindakan ACTION_DRAG_LOCATION berisi data yang valid untuk getX() dan getY() yang sesuai dengan lokasi titik sentuh. Pemroses dapat menggunakan informasi ini untuk mengubah tampilan View pada titik sentuh atau untuk menentukan posisi persis tempat pengguna dapat melepaskan konten.
  • Sebagai respons terhadap ACTION_DRAG_EXITED, pemroses harus mereset perubahan tampilan apa pun yang diterapkannya sebagai respons terhadap ACTION_DRAG_ENTERED atau ACTION_DRAG_LOCATION. Hal ini menunjukkan kepada pengguna bahwa View tersebut tidak lagi menjadi target operasi lepas yang akan segera terjadi.

Merespons operasi lepas

Jika pengguna merilis bayangan tarik di atas View, dan View sebelumnya melaporkan bahwa pengguna dapat menerima konten yang sedang ditarik, sistem akan mengirim peristiwa tarik ke View dengan jenis tindakan ACTION_DROP.

Pemroses peristiwa tarik harus melakukan hal berikut:

  1. Panggil getClipData() untuk mendapatkan objek ClipData yang awalnya disediakan dalam panggilan ke startDragAndDrop() dan proses data tersebut. Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini tidak diperlukan.

  2. Menampilkan boolean true untuk menunjukkan bahwa operasi lepas berhasil diproses, atau false jika belum. Nilai yang ditampilkan menjadi nilai yang ditampilkan oleh getResult() untuk peristiwa ACTION_DRAG_ENDED akhir. Jika sistem tidak mengirimkan peristiwa ACTION_DROP, nilai yang ditampilkan oleh getResult() untuk peristiwa ACTION_DRAG_ENDED adalah false.

Untuk peristiwa ACTION_DROP, getX() dan getY() menggunakan sistem koordinat View yang menerima operasi lepas untuk menampilkan posisi X dan Y dari titik sentuh pada saat penurunan.

Meskipun pengguna dapat merilis bayangan tarik di atas View yang pemroses peristiwa tariknya tidak menerima peristiwa tarik, area kosong di UI aplikasi Anda, atau bahkan di area di luar aplikasi Anda, Android tidak akan mengirim peristiwa dengan jenis tindakan ACTION_DROP dan hanya akan mengirim peristiwa ACTION_DRAG_ENDED.

Merespons akhir operasi tarik

Segera setelah pengguna melepas bayangan tarik, sistem akan mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED ke semua pemroses peristiwa tarik dalam aplikasi Anda. Ini menunjukkan bahwa operasi tarik telah selesai.

Setiap pemroses peristiwa tarik harus melakukan hal berikut:

  1. Jika pemroses mengubah tampilannya selama operasi, pemroses harus mereset kembali ke tampilan default-nya sebagai indikasi visual kepada pengguna bahwa operasi telah selesai.
  2. Jika diinginkan, pemroses dapat memanggil getResult() untuk mengetahui operasi tersebut lebih lanjut. Jika pemroses menampilkan true sebagai respons terhadap peristiwa dengan jenis tindakan ACTION_DROP, getResult() akan menampilkan boolean true. Dalam semua kasus lainnya, getResult() akan menampilkan boolean false, termasuk saat sistem tidak mengirim peristiwa ACTION_DROP.
  3. Untuk menunjukkan keberhasilan penyelesaian operasi lepas, pemroses harus menampilkan boolean true ke sistem. Dengan tidak menampilkan false, tanda visual yang menunjukkan drop shadow yang kembali ke sumbernya dapat menunjukkan kepada pengguna bahwa operasi tidak berhasil.

Merespons peristiwa tarik: Contoh

Semua peristiwa tarik diterima oleh metode atau pemroses peristiwa tarik. Cuplikan kode berikut adalah contoh untuk merespons peristiwa tarik:

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;

});

Menyesuaikan bayangan tarik

Anda dapat menentukan myDragShadowBuilder yang disesuaikan dengan mengganti metode di View.DragShadowBuilder. Cuplikan kode berikut membuat bayangan tarik kecil persegi panjang, abu-abu untuk 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);
    }
}