Mengimplementasikan tarik lalu lepas dengan tampilan

Anda dapat menerapkan proses tarik lalu lepas dalam tampilan dengan merespons peristiwa yang dapat memicu awal tarik lalu respons dan penggunaan peristiwa lepas.

Mulai operasi tarik

Pengguna memulai {i>drag<i} dengan {i>gesture<i}, biasanya dengan menyentuh atau mengeklik dan menyimpan item yang ingin mereka tarik.

Untuk menangani hal ini di View, buat Objek ClipData dan Objek ClipData.Item untuk data yang dipindahkan. Sebagai bagian dari ClipData, berikan metadata yang disimpan di Objek ClipDescription dalam ClipData. Untuk operasi tarik lalu lepas yang tidak mewakili perpindahan data, sebaiknya gunakan null daripada 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 bereaksi dengan memanggil DragEvent.getAction() untuk mendapatkan jenis tindakan. Di awal {i>drag<i}, metode ini akan menampilkan ACTION_DRAG_STARTED.

Sebagai respons terhadap peristiwa dengan jenis tindakan ACTION_DRAG_STARTED, peristiwa tarik pemroses harus melakukan hal berikut:

  1. Telepon 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 mewakili perpindahan data, hal ini mungkin tidak diperlukan.

  2. Jika pemroses peristiwa tarik dapat menerima peristiwa lepas, pemroses harus menampilkan true untuk memberi tahu sistem untuk terus mengirim kejadian seret ke pemroses. Jika pemroses tidak dapat menerima pelepasan, pemroses harus menampilkan false, dan sistem akan berhenti mengirimkan kejadian seret ke pemroses hingga sistem mengirim 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 terus menerima peristiwa tarik. Jenis peristiwa tarik yang diterima pemroses selama operasi tarik bergantung pada lokasi bayangan tarik dan visibilitas View pemroses. Pemroses menggunakan fungsi tarik peristiwa utama untuk memutuskan apakah peristiwa tersebut harus mengubah tampilan View.

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

  • ACTION_DRAG_ENTERED: pemroses menerima jenis tindakan peristiwa ini saat titik sentuh— menunjuk pada layar di bawah jari atau {i>mouse<i} pengguna—memasukkan kotak pembatas View pemroses.
  • ACTION_DRAG_LOCATION: setelah pemroses menerima peristiwa ACTION_DRAG_ENTERED, pemroses akan menerima peristiwa Peristiwa ACTION_DRAG_LOCATION setiap kali titik sentuh bergerak hingga titik sentuh tersebut menerima peristiwa ACTION_DRAG_EXITED. Metode getX() dan getY() mengembalikan koordinat X dan Y dari 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 mengembalikan nilai ke sistem, nilai itu 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. Tujuan pemroses dapat menggunakan informasi ini untuk mengubah tampilan View di atau menentukan posisi persis tempat pengguna dapat melepaskan saat ini.
  • Sebagai respons terhadap ACTION_DRAG_EXITED, pemroses harus mereset semua tampilan perubahan yang diterapkan 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

Saat pengguna merilis bayangan tarik di atas View, dan View sebelumnya melaporkan bahwa ia dapat menerima konten yang ditarik, sistem akan mengirimkan 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 yang disediakan dalam panggilan ke startDragAndDrop() dan memproses data. Jika operasi tarik lalu lepas tidak mewakili data gerakan, ini tidak diperlukan.

  2. Menampilkan boolean true untuk menunjukkan bahwa operasi lepas berhasil diproses, atau false jika tidak. 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 penurunan untuk menampilkan posisi X dan Y dari kontak pelanggan pada saat penurunan.

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

Merespons akhir operasi tarik

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

Setiap pemroses peristiwa tarik harus melakukan hal berikut:

  1. Jika pemroses mengubah tampilannya selama operasi, pemroses harus direset kembali ke tampilan {i>default<i}nya sebagai indikasi visual kepada pengguna bahwa selesai.
  2. Jika diinginkan, pemroses dapat memanggil getResult() untuk mengetahui operasi tersebut lebih lanjut. Jika pemroses menampilkan true sebagai respons terhadap peristiwa tindakan jenis ACTION_DROP, lalu getResult() akan menampilkan boolean true. Di semua properti lainnya kasus, getResult() akan menampilkan boolean false, termasuk saat sistem tidak mengirim peristiwa ACTION_DROP.
  3. Untuk menunjukkan keberhasilan penyelesaian operasi pelepasan, pemroses akan menampilkan boolean true ke sistem. Dengan tidak menampilkan false, isyarat visual yang menunjukkan {i>drop shadow<i} yang kembali ke sumbernya mungkin menyarankan pengguna bahwa operasi itu gagal.

Merespons peristiwa tarik: Contoh

Semua peristiwa tarik diterima oleh metode atau pemroses peristiwa tarik. Tujuan cuplikan kode berikut adalah contoh respons terhadap 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 instance bayangan tarik 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);
    }
}