Tarik lalu lepas

Dengan framework tarik lalu lepas Android, Anda dapat memungkinkan pengguna memindahkan data dengan gestur tarik lalu lepas secara interaktif. Pengguna dapat menarik teks, gambar, objek—konten apa pun yang dapat direpresentasikan oleh URI—dari View ke konten lain dalam aplikasi atau, di mode multi-jendela, di antara 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, framework dapat digunakan untuk tindakan UI lainnya. Misalnya, Anda dapat membuat aplikasi yang menggabungkan warna saat pengguna menarik sebuah ikon warna ke atas ikon warna yang lain. Namun, bagian selanjutnya dari panduan ini menjelaskan framework tarik lalu lepas dalam konteks transfer data.

Ringkasan

Operasi tarik lalu lepas dimulai saat pengguna membuat gestur UI yang diidentifikasi aplikasi Anda sebagai sinyal untuk mulai menarik data. Sebagai respons, aplikasi memberi tahu sistem bahwa operasi tarik lalu lepas dimulai. Sistem memanggil kembali ke aplikasi Anda untuk mendapatkan representasi data yang sedang ditarik ( bayangan tarik). Saat pengguna memindahkan bayangan tarik ke atas tata letak aplikasi, sistem akan mengirim peristiwa tarik ke pemroses peristiwa tarik dan metode callback yang dikaitkan dengan objek View dalam tata letak. Jika pengguna merilis bayangan tarik di atas tampilan yang dapat menerima data (target lepas), sistem akan mengirim data ke target. Operasi tarik lalu lepas berakhir saat pengguna merilis bayangan tarik terlepas dari apakah bayangan tarik melebihi target drop atau tidak.

Anda membuat pemroses peristiwa tarik dengan mengimplementasikan View.OnDragListener. Anda menetapkan 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 mulai mengirimkan peristiwa tarik. Metode ini juga menyediakan sistem dengan data yang ditarik pengguna dan metadata yang menjelaskan data tersebut. Anda dapat memanggil startDragAndDrop() di View pada 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 akan menerima data), sistem akan mengirim objek peristiwa tarik yang berisi data ke pemroses peristiwa tarik dari target lepas atau metode callback.

Pemroses peristiwa tarik dan metode callback

View menerima peristiwa tarik dengan pemroses peristiwa tarik yang mengimplementasikan View.OnDragListener atau dengan tampilan metode callback onDragEvent() . Saat 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 mengimplementasikannya sebagai class inline anonim atau ekspresi lambda. Untuk menetapkan pemroses bagi objek View, panggil setOnDragListener().

Sebagai alternatif, implementasi default onDragEvent() dapat diubah tanpa mengganti metode. Jika Anda menetapkan OnReceiveContentListener pada tampilan (lihat setOnReceiveContentListener()), metode onDragEvent() secara default akan melakukan hal berikut:

  • 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 jika data tarik lalu lepas dilepaskan pada tampilan dan OnReceiveContentListener akan menggunakan konten apa pun

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

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

Gabungan dari metode onDragEvent() dan View.OnDragListener serupa dengan gabungan dari onTouchEvent() dan View.OnTouchListener yang digunakan dengan peristiwa sentuh.

Proses tarik lalu lepas

Pada dasarnya, ada empat langkah atau status dalam proses tarik lalu lepas: Dimulai, Melanjutkan, Dilepas, 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 menjelaskan data yang ditarik

Sistem akan merespons dengan melakukan memanggil kembali ke aplikasi Anda untuk mendapatkan bayangan tarik. Sistem kemudian menampilkan bayangan tarik di perangkat.

Selanjutnya, sistem mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_STARTED ke pemroses peristiwa tarik untuk semua View dalam tata letak saat ini. Untuk terus menerima peristiwa tarik, termasuk kemungkinan peristiwa lepas, pemroses peristiwa tarik harus menampilkan true. Tindakan ini mendaftarkan pemroses ke sistem. Hanya listener terdaftar yang melanjutkan untuk menerima kejadian seret. Pada tahap ini, pemroses juga dapat mengubah tampilan objek View target lepas untuk menunjukkan bahwa tampilan dapat menerima peristiwa lepas.

Jika pemroses peristiwa tarik menampilkan false, peristiwa tarik untuk operasi saat ini hingga sistem mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED tidak akan diterima. 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 penyeretan. Saat bayangan tarik memotong kotak pembatas dari target lepas, sistem akan mengirimkan satu atau beberapa peristiwa tarik ke pemroses peristiwa tarik target. Pemroses dapat memilih untuk mengubah tampilan target lepas View sebagai respons terhadap peristiwa tersebut. Misalnya, jika peristiwa itu menunjukkan bahwa bayangan tarik telah memasuki kotak pembatas target lepas (jenis tindakan ACTION_DRAG_ENTERED), pemroses dapat bereaksi dengan menyorot View.

Dilepas

Pengguna merilis 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 ini. Pemroses diharapkan menampilkan boolean true ke sistem jika pemroses berhasil memproses data yang dilepas.

Perhatikan bahwa langkah ini hanya terjadi jika pengguna melepas bayangan tarik di dalam kotak pembatas View yang pemrosesnya terdaftar untuk menerima peristiwa tarik (target operasi lepas). Jika pengguna merilis bayangan tarik dalam situasi lain, tidak ada peristiwa tarik ACTION_DROP yang dikirim.

Berakhir

Setelah pengguna merilis bayangan tarik, dan setelah sistem mengirim (jika perlu) peristiwa tarik ke jenis tindakan ACTION_DROP, sistem akan mengirim peristiwa tarik ke jenis tindakan ACTION_DRAG_ENDED agar menunjukkan bahwa operasi tarik lalu lepas telah selesai. Hal ini dilakukan di mana pun pengguna merilis bayangan tarik. Peristiwa ini dikirim ke setiap pemroses yang terdaftar untuk menerima peristiwa tarik, meskipun pemroses tersebut menerima peristiwa ACTION_DROP.

Masing-masing dari keempat langkah tersebut dijelaskan secara lebih mendetail dalam Operasi tarik lalu lepas.

Peristiwa tarik

Sistem 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 dalam class DragEvent.

Tabel 1. Jenis tindakan DragEvent

Jenis tindakan Arti
ACTION_DRAG_STARTED Aplikasi telah memanggil startDragAndDrop() dan telah memperoleh bayangan tarik. Jika pemroses ingin terus menerima peristiwa tarik untuk operasi ini, pemroses harus menampilkan boolean true ke sistem.
ACTION_DRAG_ENTERED Bayangan tarik baru saja memasuki kotak pembatas dari 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 telah bergerak ke luar kotak pembatas View pemroses peristiwa tarik.
ACTION_DROP Bayangan tarik telah dirilis melalui 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 merilis bayangan tarik di atas apa pun yang bukan bagian dari tata letak saat ini.

Pemroses diharapkan menampilkan boolean true jika berhasil memproses peristiwa lepas. Jika tidak, proses harus menampilkan false.

ACTION_DRAG_ENDED Sistem mengakhiri operasi tarik lalu lepas. Jenis tindakan ini tidak perlu 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() (lihat tabel 2) untuk mendapatkan nilai yang ditampilkan sebagai respons terhadap ACTION_DROP. Jika peristiwa ACTION_DROP tidak terkirim, 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 diringkas dalam tabel 2. Untuk informasi selengkapnya tentang peristiwa dan data terkaitnya, lihat 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 sebuah 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 respons terhadapstartDragAndDrop(), sistem memanggil metode callback yang telah Anda tentukan di 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 (jika ada) yang dipilih pengguna untuk memulai operasi tarik.

Jika menggunakan konstruktor ini, Anda tidak perlu memperluas View.DragShadowBuilder atau mengganti metodenya. Secara default, Anda akan mendapatkan bayangan tarik yang tampilannya 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 dalam objek View.DragShadowBuilder (kolom ini 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 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 argumen tunggal, 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, sebaiknya ukuran bayangan tarik dibuat sekecil mungkin. Untuk satu item, sebaiknya Anda menggunakan ikon. Untuk beberapa pilihan, Anda dapat menggunakan setumpuk ikon, bukan gambar penuh yang menyebar memenuhi layar.

Operasi tarik lalu lepas

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

Mulai operasi tarik

Pengguna memulai operasi tarik dengan gestur tarik, biasanya dengan menyentuh lama, di 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, bukannya 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(this).apply {
        // Sets the bitmap for the ImageView from an icon bit map (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(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 was 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(this);
    
    // Set the bitmap for the ImageView from an icon bit map (defined elsewhere).
    imageView.setImageBitmap(iconBitmap);
    
    // Set the tag.
    imageView.setTag(IMAGEVIEW_TAG);
    
    // Sets a long click listener for the ImageView using an anonymous listener object that
    // implements the OnLongClickListener interface.
    imageView.setOnLongClickListener( 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 was handled.
        return true;
    });
    
  2. Cuplikan kode berikut menentukan myDragShadowBuilder dengan mengganti metode di View.DragShadowBuilder. Kode ini membuat bayangan tarik persegi panjang abu-abu kecil untuk TextView:

    Kotlin

    private class MyDragShadowBuilder(v: View) : View.DragShadowBuilder(v) {
    
        private val shadow = ColorDrawable(Color.LTGRAY)
    
        // Defines 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. This sets 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)
        }
    
        // Defines 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 v) {
    
            // Stores the View parameter.
            super(v);
    
            // Creates a draggable image that fills the Canvas provided by the system.
            shadow = new ColorDrawable(Color.LTGRAY);
        }
    
        // Defines a callback that sends the drag shadow dimensions and touch point
        // back to the system.
        @Override
        public void onProvideShadowMetrics (Point size, Point touch) {
    
            // Defines local variables
            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. This sets 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);
        }
    
        // Defines 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 ini harus bereaksi dengan memanggil DragEvent#getAction() untuk mendapatkan jenis tindakan. Pada awal proses tarik, metode ini 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 di ClipDescription yang ditampilkan untuk melihat apakah pemroses dapat menerima data 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 mengirimkan peristiwa tarik ke pemroses. Jika pemroses tidak dapat menerima peristiwa lepas, pemroses harus menampilkan false, dan sistem akan berhenti mengirimkan peristiwa tarik ke pemroses sampai sistem mengirimkan ACTION_DRAG_ENDED untuk menyimpulkan operasi tarik lalu lepas.

Perhatikan bahwa peristiwa ACTION_DRAG_STARTED, metode DragEvent berikut tidak valid: getClipData(), getX(), getY(), and 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 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 saat titik sentuh (titik pada layar di bawah jari atau mouse pengguna) telah memasuki kotak pembatas dari View pemroses.
  • ACTION_DRAG_LOCATION: Setelah menerima peristiwa ACTION_DRAG_ENTERED, dan sebelum menerima peristiwa ACTION_DRAG_EXITED, pemroses akan menerima peristiwa ACTION_DRAG_LOCATION baru setiap kali titik sentuh berpindah. Metode getX() dan getY() menampilkan koordinat X dan Y titik kontak.
  • 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 listener mengembalikan sebuah 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 getX() dan getY(), yang bersesuaian dengan lokasi titik sentuh. Pemroses dapat menggunakan informasi ini untuk mengubah tampilan View pada titik sentuh atau untuk menentukan posisi persis di mana pengguna dapat merilis bayangan tarik (yaitu, melepaskan data -nya).
  • Sebagai respon terhadap ACTION_DRAG_EXITED, pemroses perlu mereset perubahan tampilan apapun 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 konten yang sedang ditarik dapat diterima, sistem akan mengirimkan peristiwa tarik ke View dengan jenis tindakan ACTION_DROP.

Pemroses peristiwa tarik harus melakukan hal berikut:

  1. Memanggil 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 yang mengindikasikan bahwa operasi lepas berhasil diproses, atau boolean false jika operasi lepas tidak berhasil. Nilai yang ditampilkan menjadi nilai yang ditampilkan oleh getResult() untuk peristiwa ACTION_DRAG_ENDED akhir.

    Perhatikan bahwa 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 titik momen lepas.

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

Merespons akhir operasi tarik

Segera setelah pengguna merilis bayangan tarik, sistem akan mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED ke semua pemroses peristiwa tarik dalam aplikasi Anda data. 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 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 jika 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 ->

    // Handles each of the expected events.
    when (e.action) {
        DragEvent.ACTION_DRAG_STARTED -> {
            // Determines if this View can accept the dragged data.
            if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                // As an example of what your application might do, applies a blue color tint
                // to the View to indicate that it can accept data.
                (v as? ImageView)?.setColorFilter(Color.BLUE)

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

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

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

            // Returns true; the value is ignored.
            true
        }

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

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

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

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

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

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

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

            // Returns true. DragEvent.getResult() will return true.
            true
        }

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

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

            // Does a getResult(), and displays what happened.
            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()

            // Returns true; the value is ignored.
            true
        }
        else -> {
            // An unknown action type was 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) -> {

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

        case DragEvent.ACTION_DRAG_STARTED:

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

                // As an example of what your application might do, applies a blue color tint
                // to the View to indicate that it can accept data.
                ((ImageView)v).setColorFilter(Color.BLUE);

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

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

            }

            // Returns false to indicate that, during the current drag and drop operation,
            // this View will not receive events again until ACTION_DRAG_ENDED is sent.
            return false;

        case DragEvent.ACTION_DRAG_ENTERED:

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

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

            // Returns true; the value is ignored.
            return true;

        case DragEvent.ACTION_DRAG_LOCATION:

            // Ignore the event.
            return true;

        case DragEvent.ACTION_DRAG_EXITED:

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

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

            // Returns true; the value is ignored.
            return true;

        case DragEvent.ACTION_DROP:

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

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

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

            // Turns off any color tints.
            ((ImageView)v).clearColorFilter();

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

            // Returns true. DragEvent.getResult() will return true.
            return true;

        case DragEvent.ACTION_DRAG_ENDED:

            // Turns off any color tinting.
            ((ImageView)v).clearColorFilter();

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

            // Does a getResult(), and displays what happened.
            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();
            }

            // Returns true; the value is ignored.
            return true;

        // An unknown action type was 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 lain menggunakan operasi tarik lalu lepas (lihat Multi -dukungan jendela).

Aplikasi sumber menyediakan data. Operasi tarik lalu lepas dimulai di aplikasi sumber. Aplikasi target menerima data. Operasi tarik lalu lepas akan berakhir di aplikasi target.

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:

  • Aplikasi sumber harus menetapkan salah satu dari tanda DRAG_FLAG_GLOBAL_URI_READ dan DRAG_FLAG_GLOBAL_URI_WRITE, atau keduanya, yang bergantung pada baca/tulis akses ke data yang ingin diberikan oleh aplikasi sumber ke aplikasi target.
  • Aplikasi target harus memanggil requestDragAndDropPermissions() segera 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.

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 mendapatkan contoh yang lebih lengkap.

Aktivitas tarik lalu lepas sumber

Kotlin

// Drag a file stored in internal storage. The file is in an "images/" directory.
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 allow for 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 under 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 allow for 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 for 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 afterwards because it's
            // no longer needed.
            dropPermissions.release()
            return@setOnDragListener true
        }

        // Implement logic for other DragEvent cases here.

        // An unknown action type was 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 afterwards 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.

Target operasi lepas

DropHelper#configureView() adalah metode statis yang kelebihan muatan yang memungkinkan Anda menentukan target operasi lepas. Parameter mencakup:

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 tema (atau aksen), radius sudut sorotan ditetapkan ke 16 dp, dan daftar EditText kosong (lihat Drop target configuration di bawah).

Konfigurasi target operasi lepas

Dengan class dalam DropHelper.Options, Anda dapat mengonfigurasi target operasi lepas. Anda memberikan instance class ke metode DropHelper.configureView(Activity, View, String[], Options, OnReceiveContentListener) (lihat Melepas target di atas).

Sorotan target operasi lepas

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

Gunakan class DropHelper.Options.Builder untuk membuat instance DropHelper.Options dan menetapkan opsi konfigurasi, misalnya:

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();

EditText komponen pada target operasi lepas

DropHelper juga mengontrol fokus dalam target operasi lepas saat 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 , Anda harus memberikan daftar komponen untuk DropHelper.Options.Builder#addInnerEditTexts(EditText...) untuk memastikan bahwa pelepasan sorotan target 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 untuk 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();

Penanganan data target operasi lepas

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

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

  • Semua tampilan, jika pengguna menjalankan Android 12 atau lebih tinggi
  • AppCompatEditText hingga Android 7.0

Jenis MIME, izin, dan validasi konten

Pemeriksaan jenis MIME DropHelper didasarkan pada ClipDescription tarik lalu lepas yang dibuat oleh aplikasi yang menyediakan data tarik lalu lepas. Anda harus memvalidasi ClipDescription untuk memastikan jenis MIME telah ditetapkan dengan benar.

DropHelper meminta semua izin akses untuk URI konten yang terdapat dalam tarik lalu lepas ClipData (lihat DragAndDropPermissions). Izin tersebut memungkinkan Anda mengatasi URI konten saat memproses tarik lalu lepas data.

DropHelper tidak memvalidasi data yang ditampilkan oleh penyedia konten saat menyelesaikan URI dalam data yang dilepas. Anda harus memeriksa null dan memverifikasi ketepatan data yang diselesaikan.

Referensi tambahan