Mengaktifkan tarik lalu lepas

Framework tarik lalu lepas Android memungkinkan Anda menambahkan kemampuan tarik lalu lepas interaktif ke aplikasi. Dengan tarik lalu lepas, pengguna dapat menyalin atau memindahkan teks, gambar, objek, dan konten apa pun yang dapat direpresentasikan oleh URI, dari satu View ke View lainnya dalam aplikasi, atau antar-aplikasi dalam mode multi-aplikasi.

String teks dan gambar yang ditarik lalu dilepas di dalam aplikasi. String teks dan gambar ditarik dan dilepas di antara aplikasi dalam mode layar terpisah.
Gambar 1. Tarik lalu lepas dalam aplikasi.
Gambar 2. Tarik lalu lepas antar-aplikasi.

Framework ini mencakup class peristiwa tarik, pemroses tarik, serta class dan metode helper. Meskipun utamanya dirancang untuk mengaktifkan transfer data, Anda dapat menggunakan framework untuk tindakan UI lainnya. Misalnya, Anda dapat membuat aplikasi yang menggabungkan warna saat pengguna menarik ikon warna ke atas ikon lain. Namun, bagian lain dari dokumen menjelaskan framework tarik lalu lepas dalam konteks transfer data.

Ringkasan

Operasi tarik lalu lepas dimulai saat pengguna membuat gestur UI yang dikenali aplikasi Anda sebagai sinyal untuk mulai menarik data. Sebagai respons, aplikasi akan memberi tahu sistem bahwa operasi tarik lalu lepas dimulai. Sistem melakukan callback ke aplikasi Anda untuk mendapatkan representasi data yang sedang ditarik, yang disebut bayangan tarik.

Saat pengguna memindahkan bayangan tarik ke atas tata letak aplikasi, sistem akan mengirimkan peristiwa tarik ke pemroses peristiwa tarik dan metode callback yang terkait dengan objek View dalam tata letak. Jika pengguna melepas bayangan tarik di atas tampilan yang dapat menerima data (target lepas), sistem akan mengirimkan data ke target. Operasi tarik lalu lepas berakhir saat pengguna merilis bayangan tarik, terlepas dari apakah bayangan tarik berada di atas target lepas atau tidak.

Buat pemroses peristiwa tarik dengan mengimplementasikan View.OnDragListener. Tetapkan pemroses untuk target lepas dengan metode setOnDragListener() objek View. Setiap tampilan dalam tata letak juga memiliki metode callback onDragEvent() .

Aplikasi Anda memberi tahu sistem untuk memulai operasi tarik lalu lepas dengan memanggil metode startDragAndDrop(), yang memberi tahu sistem untuk mengirim peristiwa tarik. Metode ini juga memberikan data yang ditarik pengguna dan metadata yang menjelaskan data kepada sistem. Anda dapat memanggil startDragAndDrop() di View mana pun dalam tata letak saat ini. Sistem menggunakan objek View hanya untuk mendapatkan akses ke setelan global dalam tata letak.

Selama operasi tarik lalu lepas, sistem akan mengirimkan peristiwa tarik ke pemroses peristiwa tarik atau metode callback objek View dalam tata letak. Pemroses atau metode callback menggunakan metadata untuk memutuskan apakah ingin menerima data saat data tersebut dilepas. Jika pengguna melepaskan data pada target lepas—View yang menerima data—sistem akan mengirimkan objek peristiwa tarik yang berisi data ke metode callback atau pemroses peristiwa tarik target lepas.

Pemroses peristiwa tarik dan metode callback

View menerima peristiwa tarik dengan pemroses peristiwa tarik yang mengimplementasikan View.OnDragListener, atau dengan metode callback onDragEvent() tampilan. Saat sistem memanggil metode atau pemroses, sistem akan memberikan argumen DragEvent.

Umumnya, saat menggunakan pemroses lebih baik menggunakan metode callback. Saat mendesain UI, biasanya Anda tidak membuat subclass untuk class View, tetapi menggunakan metode callback akan memaksa Anda membuat subclass untuk mengganti metode. Sebagai perbandingan, Anda dapat mengimplementasikan satu class pemroses lalu menggunakannya dengan beberapa objek View yang berbeda. Anda juga dapat menerapkannya sebagai class inline anonim atau ekspresi lambda. Untuk menetapkan pemroses bagi objek View, panggil setOnDragListener().

Sebagai alternatif, Anda dapat mengubah implementasi default onDragEvent() tanpa mengganti metode. Menetapkan OnReceiveContentListener pada tampilan; untuk detail selengkapnya, lihat setOnReceiveContentListener(). Metode onDragEvent() kemudian melakukan hal berikut secara default:

  • Menampilkan true sebagai respons terhadap panggilan ke startDragAndDrop().
  • Memanggil performReceiveContent() jika data tarik lalu lepas dilepaskan pada tampilan. Data diteruskan ke metode sebagai objek ContentInfo. Metode ini memanggil OnReceiveContentListener.

  • Menampilkan true (benar) jika data tarik lalu lepas dilepas pada tampilan dan OnReceiveContentListener menggunakan konten apa pun.

Tentukan OnReceiveContentListener untuk menangani data khusus untuk aplikasi Anda. Untuk kompatibilitas mundur hingga API level 24, gunakan versi Jetpack OnReceiveContentListener.

Anda dapat memiliki pemroses peristiwa tarik dan metode callback untuk objek View, dalam hal ini sistem akan memanggil pemroses terlebih dahulu. Sistem tidak memanggil metode callback kecuali jika pemroses menampilkan false.

Kombinasi metode onDragEvent() dan View.OnDragListener analog dengan kombinasi onTouchEvent() dan View.OnTouchListener yang digunakan dengan peristiwa sentuh.

Proses tarik lalu lepas

Ada empat langkah atau status dalam proses tarik lalu lepas: dimulai, melanjutkan, melepas, dan berakhir.

Dimulai

Sebagai respons terhadap gestur tarik pengguna, aplikasi Anda akan memanggil startDragAndDrop() untuk memberi tahu sistem agar memulai operasi tarik lalu lepas. Argumen metode memberikan hal berikut:

  • Data yang akan ditarik.
  • Callback untuk menggambar bayangan tarik
  • Metadata yang mendeskripsikan data yang ditarik: Sistem merespons dengan melakukan callback ke aplikasi Anda untuk mendapatkan bayangan tarik. Sistem kemudian menampilkan bayangan tarik di perangkat. : Selanjutnya, sistem akan mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_STARTED ke pemroses peristiwa tarik untuk semua objek View dalam tata letak saat ini. Untuk terus menerima peristiwa tarik, termasuk kemungkinan peristiwa lepas, pemroses peristiwa tarik harus menampilkan true. Tindakan ini akan mendaftarkan pemroses ke sistem. Hanya pemroses terdaftar yang akan terus menerima peristiwa tarik. Pada tahap ini, pemroses juga dapat mengubah tampilan objek View target operasi lepas untuk menunjukkan bahwa tampilan dapat menerima peristiwa lepas. : Jika pemroses peristiwa tarik menampilkan false, pemroses tidak akan menerima peristiwa tarik untuk operasi saat ini hingga sistem mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED. Dengan menampilkan false, pemroses memberi tahu sistem bahwa ia tidak tertarik dengan operasi tarik lalu lepas dan tidak ingin menerima data yang ditarik.
Melanjutkan
Pengguna melanjutkan proses tarik. Saat bayangan tarik berpotongan dengan kotak pembatas target lepas, sistem akan mengirimkan satu atau beberapa peristiwa tarik ke pemroses peristiwa tarik target. Pemroses dapat mengubah tampilan target lepas View sebagai respons terhadap peristiwa tersebut. Misalnya, jika peristiwa menunjukkan bahwa bayangan tarik memasuki kotak pembatas target lepas—jenis tindakan ACTION_DRAG_ENTERED—pemroses dapat bereaksi dengan menyoroti View.
Dilepas
Pengguna melepas bayangan tarik di dalam kotak pembatas target lepas. Sistem mengirimkan peristiwa tarik ke pemroses target lepas dengan jenis tindakan ACTION_DROP. Objek peristiwa tarik berisi data yang diteruskan ke sistem dalam panggilan ke startDragAndDrop() yang memulai operasi. Pemroses diharapkan menampilkan boolean true ke sistem jika pemroses berhasil memproses data yang dilepas. : Langkah ini hanya terjadi jika pengguna melepas bayangan tarik di dalam kotak pembatas View yang pemrosesnya terdaftar untuk menerima peristiwa tarik (target lepas). Jika pengguna melepas bayangan tarik dalam situasi lain, tidak ada peristiwa tarik ACTION_DROP yang dikirim.
Berakhir

Setelah pengguna melepas bayangan tarik, dan setelah sistem mengirim,

peristiwa tarik dengan jenis tindakan ACTION_DROP, jika perlu, sistem akan mengirim peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED untuk menunjukkan bahwa operasi tarik lalu lepas telah selesai. Hal ini dilakukan di mana pun pengguna melepaskan bayangan tarik. Peristiwa ini dikirim ke setiap pemroses yang terdaftar untuk menerima peristiwa tarik, meskipun pemroses juga menerima peristiwa ACTION_DROP.

Setiap langkah ini dijelaskan secara lebih mendetail di bagian yang disebut Operasi tarik lalu lepas.

Peristiwa tarik

Sistem akan mengirimkan peristiwa tarik dalam bentuk objek DragEvent, yang berisi jenis tindakan yang menjelaskan apa yang terjadi dalam proses tarik lalu lepas. Tergantung pada jenis tindakan, objek juga dapat berisi data lain.

Pemroses peristiwa tarik menerima objek DragEvent. Untuk mendapatkan jenis tindakan, pemroses memanggil DragEvent.getAction(). Ada enam kemungkinan nilai yang ditentukan oleh konstanta di class DragEvent, yang dijelaskan dalam tabel 1:

Tabel 1. Jenis tindakan DragEvent

Jenis tindakan Arti
ACTION_DRAG_STARTED Aplikasi memanggil startDragAndDrop() dan mendapatkan bayangan tarik. Jika pemroses ingin terus menerima peristiwa tarik untuk operasi ini, pemroses harus menampilkan boolean true ke sistem.
ACTION_DRAG_ENTERED Bayangan tarik memasuki kotak pembatas View pemroses peristiwa tarik. Ini adalah jenis tindakan peristiwa pertama yang diterima pemroses saat bayangan tarik memasuki kotak pembatas.
ACTION_DRAG_LOCATION Setelah peristiwa ACTION_DRAG_ENTERED, bayangan tarik masih berada dalam kotak pembatas View pemroses peristiwa tarik.
ACTION_DRAG_EXITED Setelah ACTION_DRAG_ENTERED dan setidaknya satu peristiwa ACTION_DRAG_LOCATION, bayangan tarik akan bergerak ke luar kotak pembatas View pemroses peristiwa tarik.
ACTION_DROP Bayangan tarik terlepas di atas View pemroses peristiwa tarik. Jenis tindakan ini dikirim ke pemroses objek View hanya jika pemroses menampilkan boolean true sebagai respons terhadap peristiwa tarik ACTION_DRAG_STARTED. Jenis tindakan ini tidak dikirim jika pengguna merilis bayangan tarik di atas View yang pemrosesnya tidak terdaftar, atau jika pengguna melepaskan bayangan tarik di atas apa pun yang bukan bagian dari tata letak saat ini.

Pemroses menampilkan boolean true jika berhasil memproses peristiwa lepas. Jika tidak, metode ini harus menampilkan false.

ACTION_DRAG_ENDED Sistem mengakhiri operasi tarik lalu lepas. Jenis tindakan ini tidak harus didahului oleh peristiwa ACTION_DROP. Jika sistem mengirim ACTION_DROP, penerimaan jenis tindakan ACTION_DRAG_ENDED bukan berarti bahwa operasi lepas berhasil. Pemroses harus memanggil getResult(), seperti yang ditunjukkan pada tabel 2, untuk mendapatkan nilai yang ditampilkan sebagai respons terhadap ACTION_DROP. Jika peristiwa ACTION_DROP tidak dikirim, getResult() akan menampilkan false.

Objek DragEvent juga berisi data dan metadata yang disediakan aplikasi Anda ke sistem dalam panggilan ke startDragAndDrop(). Sebagian data hanya valid untuk jenis tindakan tertentu seperti yang dirangkum dalam tabel 2. Untuk mengetahui informasi selengkapnya tentang peristiwa dan data terkaitnya, lihat bagian yang disebut Operasi tarik lalu lepas.

Tabel 2. Data DragEvent yang valid menurut jenis tindakan

getAction()
nilai
getClipDescription()
nilai
getLocalState()
nilai
getX()
nilai
getY()
nilai
getClipData()
nilai
getResult()
nilai
ACTION_DRAG_STARTED &periksa; &periksa; &periksa; &periksa;    
ACTION_DRAG_ENTERED &periksa; &periksa;        
ACTION_DRAG_LOCATION &periksa; &periksa; &periksa; &periksa;    
ACTION_DRAG_EXITED &periksa; &periksa;        
ACTION_DROP &periksa; &periksa; &periksa; &periksa; &periksa;  
ACTION_DRAG_ENDED   &periksa;       &periksa;

Metode DragEvent getAction(), describeContents(), writeToParcel(), dan toString() selalu menampilkan data yang valid.

Jika suatu metode tidak berisi data yang valid untuk jenis tindakan tertentu, metode tersebut akan menampilkan null atau 0, bergantung pada jenis hasilnya.

Bayangan tarik

Selama operasi tarik lalu lepas, sistem akan menampilkan gambar yang ditarik oleh pengguna. Untuk pemindahan data, gambar ini mewakili data yang sedang ditarik. Untuk operasi lainnya, gambar ini mewakili beberapa aspek operasi tarik.

Gambar ini disebut bayangan tarik. Anda membuatnya dengan metode yang Anda deklarasikan untuk objek View.DragShadowBuilder. Anda meneruskan builder ke sistem saat Anda memulai operasi tarik lalu lepas menggunakan startDragAndDrop(). Sebagai bagian dari responsnya terhadap startDragAndDrop(), sistem akan memanggil metode callback yang Anda tentukan dalam View.DragShadowBuilder untuk mendapatkan bayangan tarik.

Class View.DragShadowBuilder memiliki dua konstruktor:

View.DragShadowBuilder(View)

Konstruktor ini menerima objek View apa pun dari aplikasi Anda. Konstruktor menyimpan objek View dalam objek View.DragShadowBuilder, sehingga callback dapat mengaksesnya untuk mengonstruksi bayangan tarik. Tampilan tidak harus berupa View yang dipilih pengguna untuk memulai operasi tarik.

Jika menggunakan konstruktor ini, Anda tidak perlu memperluas View.DragShadowBuilder atau mengganti metodenya. Secara default, Anda mendapatkan bayangan tarik yang memiliki tampilan yang sama dengan View yang Anda teruskan sebagai argumen, yang berpusat di bawah lokasi tempat pengguna menyentuh layar.

View.DragShadowBuilder()

Jika Anda menggunakan konstruktor ini, tidak ada objek View yang tersedia di objek View.DragShadowBuilder. Kolom ditetapkan ke null. Anda harus memperluas View.DragShadowBuilder dan mengganti metodenya, atau Anda akan mendapatkan bayangan tarik yang tidak terlihat. Sistem tidak menampilkan error.

Class View.DragShadowBuilder memiliki dua metode yang bersama-sama membuat bayangan tarik:

onProvideShadowMetrics()

Sistem akan memanggil metode ini segera setelah Anda memanggil startDragAndDrop(). Gunakan metode ini untuk mengirim dimensi dan titik sentuh bayangan tarik ke sistem. Metode ini memiliki dua parameter:

outShadowSize: objek Point. Lebar bayangan tarik masuk di x, dan tingginya masuk di y.

outShadowTouchPoint: objek Point. Titik sentuh adalah lokasi dalam bayangan tarik yang harus berada di bawah jari pengguna selama operasi tarik. Posisi X-nya masuk di x sedangkan posisi Y-nya masuk di y.

onDrawShadow()

Segera setelah panggilan ke onProvideShadowMetrics(), sistem akan memanggil onDrawShadow() untuk membuat bayangan tarik. Metode ini memiliki satu argumen, objek Canvas yang dibuat oleh sistem dari parameter yang Anda berikan di onProvideShadowMetrics(). Metode ini menggambar bayangan tarik pada Canvas yang disediakan.

Untuk meningkatkan performa, pertahankan ukuran bayangan tarik agar tetap kecil. Untuk satu item, Anda mungkin ingin menggunakan ikon. Untuk beberapa pilihan, Anda mungkin ingin menggunakan ikon dalam stack, bukan gambar penuh yang tersebar di layar.

Operasi tarik lalu lepas

Bagian ini menunjukkan langkah demi langkah cara memulai tarik, merespons peristiwa selama tarik, merespons peristiwa lepas, dan mengakhiri operasi tarik lalu lepas.

Mulai operasi tarik

Pengguna memulai penarikan dengan gestur tarik, biasanya sentuh lama, pada objek View. Sebagai respons, aplikasi Anda harus melakukan hal berikut:

  1. 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.
            val myShadow = MyDragShadowBuilder(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.
    View.DragShadowBuilder myShadow = new MyDragShadowBuilder(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;
    });
    
  2. Tentukan myDragShadowBuilder dengan mengganti metode di View.DragShadowBuilder. Cuplikan kode berikut membuat bayangan tarik persegi panjang, abu-abu kecil 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);
    }
    }
    

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. Pada awal penarikan, metode ini akan menampilkan ACTION_DRAG_STARTED.

Sebagai respons terhadap 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, hal ini mungkin tidak diperlukan.

  2. Jika pemroses peristiwa tarik dapat menerima peristiwa lepas, pemroses harus menampilkan true untuk memberi tahu sistem agar terus mengirimkan peristiwa tarik ke pemroses. Jika pemroses tidak dapat menerima peristiwa lepas, pemroses harus menampilkan false, dan sistem akan berhenti mengirim 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 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 pemroses harus mengubah tampilan View-nya atau tidak.

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

  • ACTION_DRAG_ENTERED: pemroses menerima jenis tindakan peristiwa ini jika 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.
  • 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 menentukan posisi persis di mana pengguna dapat melepaskan bayangan tarik—yaitu, melepaskan data.
  • Sebagai respons terhadap ACTION_DRAG_EXITED, pemroses harus mereset setiap perubahan tampilan 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 melepas bayangan tarik di atas View, dan View sebelumnya melaporkan bahwa ia dapat menerima konten yang sedang 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 disediakan dalam panggilan ke startDragAndDrop() dan proses data. 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 operasi lepas tidak berhasil. 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 titik sentuh pada saat penurunan.

Sistem memungkinkan pengguna merilis bayangan tarik di atas View yang pemroses peristiwa tariknya tidak menerima peristiwa tarik. API ini juga memungkinkan pengguna melepaskan bayangan tarik di atas area kosong UI aplikasi atau di atas area di luar aplikasi Anda. Dalam semua kasus ini, sistem tidak mengirim peristiwa dengan jenis tindakan ACTION_DROP, meskipun sistem mengirim peristiwa ACTION_DRAG_ENDED.

Merespons akhir operasi tarik

Segera setelah pengguna melepaskan bayangan tarik, sistem akan mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED ke semua pemroses peristiwa tarik dalam aplikasi Anda. Hal ini menunjukkan bahwa operasi tarik lalu lepas telah berakhir.

Setiap pemroses peristiwa tarik harus melakukan hal berikut:

  1. Jika pemroses mengubah tampilan objek View-nya selama operasi, pemroses harus mereset View ke tampilan default-nya. Ini adalah indikasi visual bagi 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 tarik lalu lepas, pemroses harus menampilkan boolean true ke sistem.

Merespons peristiwa tarik: Contoh

Semua peristiwa tarik diterima oleh metode atau pemroses peristiwa tarik. Cuplikan kode berikut adalah contoh sederhana 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;

});

Tarik lalu lepas pada mode multi-aplikasi

Perangkat yang menjalankan Android 7.0 (API level 24) atau yang lebih baru mendukung mode multi-aplikasi, yang memungkinkan pengguna memindahkan data dari satu aplikasi ke aplikasi lainnya menggunakan operasi tarik lalu lepas. Untuk mengetahui informasi selengkapnya, lihat Dukungan multi-aplikasi.

Aplikasi sumber, tempat operasi tarik lalu lepas dimulai, akan menyediakan data. Aplikasi target, tempat operasi tarik lalu lepas berakhir, menerima data.

Saat memulai operasi tarik lalu lepas, aplikasi sumber harus menetapkan tanda DRAG_FLAG_GLOBAL untuk menunjukkan bahwa pengguna dapat menarik data ke aplikasi lain.

Karena data berpindah melintasi batas aplikasi, aplikasi membagikan akses ke data menggunakan URI konten. Hal ini memerlukan hal berikut:

  • Aplikasi sumber harus menetapkan salah satu dari tanda DRAG_FLAG_GLOBAL_URI_READ dan DRAG_FLAG_GLOBAL_URI_WRITE, atau keduanya, bergantung pada akses baca atau tulis ke data yang ingin diberikan oleh aplikasi sumber ke aplikasi target.
  • Aplikasi target harus segera memanggil requestDragAndDropPermissions() sebelum menangani data yang ditarik pengguna ke dalam aplikasi. Jika aplikasi target tidak lagi memerlukan akses ke data tarik lalu lepas, aplikasi tersebut dapat memanggil release() pada objek yang ditampilkan dari requestDragAndDropPermissions(). Jika tidak, izin akan dirilis saat aktivitas yang memuatnya dihancurkan. Jika implementasi Anda melibatkan dimulainya Aktivitas baru untuk memproses URI yang hilang, Anda perlu memberikan izin yang sama pada Aktivitas baru. Anda harus menetapkan data klip dan tanda:

    Kotlin

    intent.setClipData(clipData)
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    

    Java

    intent.setClipData(clipData);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

Cuplikan kode berikut menunjukkan cara merilis akses hanya baca ke data tarik lalu lepas segera setelah operasi tarik lalu lepas berlangsung. Lihat contoh DragAndDrop di GitHub untuk mengetahui contoh yang lebih lengkap.

Aktivitas tarik lalu lepas sumber

Kotlin

// Drag a file stored in an images/ directory in internal storage.
val internalImagesDir = File(context.filesDir, "images")
val imageFile = File(internalImagesDir, imageFilename)
val uri = FileProvider.getUriForFile(context, contentAuthority, imageFile)

val listener = OnDragStartListener@{ view: View, _: DragStartHelper ->
    val clipData = ClipData(ClipDescription("Image Description",
                                            arrayOf("image/*")),
                            ClipData.Item(uri))
    // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps.
    // This example provides read-only access to the data.
    val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
    return@OnDragStartListener view.startDragAndDrop(clipData,
                                                     View.DragShadowBuilder(view),
                                                     null,
                                                     flags)
}

// Container where the image originally appears in the source app.
val srcImageView = findViewById<ImageView>(R.id.imageView)

// Detect and start the drag event.
DragStartHelper(srcImageView, listener).apply {
    attach()
}

Java

// Drag a file stored in an images/ directory in internal storage.
File internalImagesDir = new File(context.getFilesDir(), "images");
File imageFile = new File(internalImagesDir, imageFilename);
final Uri uri = FileProvider.getUriForFile(context, contentAuthority, imageFile);

// Container where the image originally appears in the source app.
ImageView srcImageView = findViewById(R.id.imageView);

// Enable the view to detect and start the drag event.
new DragStartHelper(srcImageView, (view, helper) -> {
    ClipData clipData = new ClipData(new ClipDescription("Image Description",
                                                          new String[] {"image/*"}),
                                     new ClipData.Item(uri));
    // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps.
    // This example provides read-only access to the data.
    int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ;
    return view.startDragAndDrop(clipData,
                                 new View.DragShadowBuilder(view),
                                 null,
                                 flags);
}).attach();

Aktivitas tarik lalu lepas target

Kotlin

// Container where the image is to be dropped in the target app.
val targetImageView = findViewById<ImageView>(R.id.imageView)

targetImageView.setOnDragListener { view, event ->

    when (event.action) {

        ACTION_DROP -> {
            val imageItem: ClipData.Item = event.clipData.getItemAt(0)
            val uri = imageItem.uri

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            val dropPermissions = requestDragAndDropPermissions(event)
            (view as ImageView).setImageURI(uri)

            // Release the permission immediately afterward because it's no
            // longer needed.
            dropPermissions.release()
            return@setOnDragListener true
        }

        // Implement logic for other DragEvent cases here.

        // An unknown action type is received.
        else -> {
            Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
            return@setOnDragListener false
        }

    }
}

Java

// Container where the image is to be dropped in the target app.
ImageView targetImageView = findViewById(R.id.imageView);

targetImageView.setOnDragListener( (view, event) -> {

    switch (event.getAction()) {

        case ACTION_DROP:
            ClipData.Item imageItem = event.getClipData().getItemAt(0);
            Uri uri = imageItem.getUri();

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            DragAndDropPermissions dropPermissions =
                requestDragAndDropPermissions(event);

            ((ImageView)view).setImageURI(uri);

            // Release the permission immediately afterward because it's no
            // longer needed.
            dropPermissions.release();

            return true;

        // Implement logic for other DragEvent cases here.

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

    return false;
});

DropHelper untuk tarik lalu lepas sederhana

Class DropHelper menyederhanakan implementasi kemampuan tarik lalu lepas. Anggota library Jetpack DragAndDrop, DropHelper menyediakan kompatibilitas mundur hingga API level 24.

Gunakan DropHelper untuk menentukan target operasi lepas, menyesuaikan sorotan target lepas, serta menentukan cara penanganan data yang dilepas.

Menentukan target operasi lepas

DropHelper.configureView() adalah metode statis yang kelebihan beban, yang memungkinkan Anda menentukan target operasi lepas. Parameternya mencakup hal berikut:

Misalnya, untuk membuat target lepas yang menerima gambar, gunakan salah satu panggilan metode berikut:

Kotlin

configureView(
    myActivity,
    targetView,
    arrayOf("image/*"),
    options,
    onReceiveContentListener)

// or

configureView(
    myActivity,
    targetView,
    arrayOf("image/*"),
    onReceiveContentListener)

Java

DropHelper.configureView(
    myActivity,
    targetView,
    new String[] {"image/*"},
    options,
    onReceiveContentlistener);

// or

DropHelper.configureView(
    myActivity,
    targetView,
    new String[] {"image/*"},
    onReceiveContentlistener);

Panggilan kedua menghilangkan opsi konfigurasi target lepas, dalam hal ini warna sorotan target lepas disetel ke warna sekunder (atau aksen) tema, radius sudut sorotan disetel ke 16 dp, dan daftar komponen EditText kosong. Lihat bagian berikut untuk mengetahui detailnya.

Mengonfigurasi target operasi lepas

Class dalam DropHelper.Options memungkinkan Anda mengonfigurasi target operasi lepas. Berikan instance class ke metode DropHelper.configureView(Activity, View, String[], Options, OnReceiveContentListener). Lihat bagian sebelumnya untuk informasi selengkapnya.

Sesuaikan penyorotan target lepas

DropHelper mengonfigurasi target operasi lepas untuk menampilkan sorotan saat pengguna menarik konten ke target. DropHelper menyediakan gaya visual default, dan DropHelper.Options memungkinkan Anda menetapkan warna sorotan dan menentukan radius sudut persegi panjang sorotan.

Gunakan class DropHelper.Options.Builder untuk membuat instance DropHelper.Options dan menetapkan opsi konfigurasi, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

val options: DropHelper.Options = DropHelper.Options.Builder()
                                      .setHighlightColor(getColor(R.color.purple_300))
                                      .setHighlightCornerRadiusPx(resources.getDimensionPixelSize(R.dimen.drop_target_corner_radius))
                                      .build()

Java

DropHelper.Options options = new DropHelper.Options.Builder()
                                     .setHighlightColor(getColor(R.color.purple_300))
                                     .setHighlightCornerRadiusPx(getResources().getDimensionPixelSize(R.dimen.drop_target_corner_radius))
                                     .build();

Menangani komponen EditText dalam target operasi lepas

DropHelper juga mengontrol fokus dalam target operasi lepas jika target berisi kolom teks yang dapat diedit.

Target lepas dapat berupa tampilan tunggal atau hierarki tampilan. Jika hierarki tampilan target lepas berisi satu atau beberapa komponen EditText, berikan daftar komponen ke DropHelper.Options.Builder.addInnerEditTexts(EditText...) untuk memastikan bahwa penandaan target lepas dan penanganan data teks berfungsi dengan benar.

DropHelper mencegah komponen EditText dalam hierarki tampilan target lepas agar tidak mencuri fokus dari tampilan yang memuatnya selama interaksi tarik.

Selain itu, jika tarik lalu lepas ClipData menyertakan data teks dan URI, DropHelper akan memilih salah satu komponen EditText dalam target lepas untuk menangani data teks. Pemilihan didasarkan pada urutan prioritas berikut:

  1. EditText tempat ClipData dilepas.
  2. EditText yang berisi kursor teks (tanda sisipan).
  3. EditText pertama yang diberikan ke panggilan ke DropHelper.Options.Builder.addInnerEditTexts(EditText...).

Untuk menetapkan EditText sebagai pengendali data teks default, teruskan EditText sebagai argumen pertama dari panggilan tersebut ke DropHelper.Options.Builder.addInnerEditTexts(EditText...). Misalnya, jika target lepas Anda menangani gambar, tetapi berisi kolom teks yang dapat diedit T1, T2, dan T3, buat T2 sebagai default sebagai berikut:

Kotlin

val options: DropHelper.Options = DropHelper.Options.Builder()
                                      .addInnerEditTexts(T2, T1, T3)
                                      .build()

Java

DropHelper.Options options = new DropHelper.Options.Builder()
                                     .addInnerEditTexts(T2, T1, T3)
                                     .build();

Menangani data di target operasi lepas

Metode DropHelper.configureView() menerima OnReceiveContentListener yang Anda buat untuk menangani ClipData tarik lalu lepas. Data tarik lalu lepas diberikan ke pemroses dalam objek ContentInfoCompat. Data teks ada di objek. Media, seperti gambar, direpresentasikan oleh URI.

OnReceiveContentListener juga menangani data yang diberikan ke target lepas oleh interaksi pengguna selain tarik lalu lepas—seperti salin dan tempel—saat DropHelper.configureView() digunakan untuk mengonfigurasi jenis tampilan berikut:

  • Semua tampilan, jika pengguna menjalankan Android 12 atau yang lebih baru.
  • AppCompatEditText, jika pengguna menjalankan versi Android hingga Android 7.0.

Jenis MIME, izin, dan validasi konten

Pemeriksaan jenis MIME oleh DropHelper didasarkan pada ClipDescription tarik lalu lepas, yang dibuat oleh aplikasi yang menyediakan data tarik lalu lepas. Validasi ClipDescription untuk memastikan jenis MIME disetel dengan benar.

DropHelper meminta semua izin akses untuk URI konten yang terdapat dalam tarik lalu lepas ClipData. Untuk mengetahui informasi selengkapnya, lihat DragAndDropPermissions. Izin ini memungkinkan Anda menyelesaikan URI konten saat memproses data tarik lalu lepas.

DropHelper tidak memvalidasi data yang ditampilkan oleh penyedia konten saat menyelesaikan URI dalam data yang dilepas. Periksa null dan verifikasi ketepatan data yang diselesaikan.