Framework tarik lalu lepas Android memungkinkan Anda menambahkan kemampuan tarik lalu lepas
secara interaktif ke aplikasi Anda. Dengan tarik lalu lepas, pengguna dapat menyalin atau memindahkan
teks, gambar, objek—konten apa pun yang dapat direpresentasikan oleh
URI—dari satu View
ke yang lain dalam
aplikasi, atau dalam mode multi-aplikasi, di antara 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 tampilanData diteruskan ke metode sebagai objek
ContentInfo
. Metode ini memanggilOnReceiveContentListener
.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 semuaView
dalam tata letak saat ini. Untuk terus menerima peristiwa tarik, termasuk kemungkinan peristiwa lepas, pemroses peristiwa tarik harus menampilkantrue
. Tindakan ini mendaftarkan pemroses ke sistem. Hanya pemroses terdaftar yang akan terus menerima peristiwa tarik. Pada tahap ini, pemroses juga dapat mengubah tampilan objekView
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 tindakanACTION_DRAG_ENDED
tidak akan diterima. Dengan menampilkanfalse
, 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 tindakanACTION_DRAG_ENTERED
), pemroses dapat bereaksi dengan menyorotView
.- 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 kestartDragAndDrop()
yang memulai operasi ini. Pemroses diharapkan menampilkan booleantrue
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 tarikACTION_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 tindakanACTION_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 peristiwaACTION_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 |
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 objekView
dalam objekView.DragShadowBuilder
, sehingga callback dapat mengaksesnya untuk mengonstruksi bayangan tarik. Tampilan tidak harus berupaView
(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 denganView
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 objekView.DragShadowBuilder
(kolom ini ditetapkan kenull
). Anda harus memperluasView.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:onDrawShadow()
Segera setelah panggilan ke
onProvideShadowMetrics()
, sistem akan memanggilonDrawShadow()
untuk membuat bayangan tarik. Metode ini memiliki argumen tunggal, objekCanvas
yang dibuat oleh sistem dari parameter yang Anda berikan dionProvideShadowMetrics()
. Metode ini menggambar bayangan tarik padaCanvas
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:
Buat objek
ClipData
dan objekClipData.Item
untuk data yang sedang dipindahkan. Sebagai bagian dariClipData
, berikan metadata yang disimpan dalam objekClipDescription
dalamClipData
. Untuk operasi tarik lalu lepas yang tidak merepresentasikan perpindahan data, Anda dapat menggunakannull
, bukannya objek yang sebenarnya.Misalnya, cuplikan kode ini menunjukkan cara merespons gestur sentuh lama di
ImageView
dengan membuat objekClipData
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; });
Cuplikan kode berikut menentukan
myDragShadowBuilder
dengan mengganti metode diView.DragShadowBuilder
. Kode ini membuat bayangan tarik persegi panjang abu-abu kecil untukTextView
: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:
Panggil
DragEvent#getClipDescription()
dan gunakan metode jenis MIME diClipDescription
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.
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 menampilkanfalse
, dan sistem akan berhenti mengirimkan peristiwa tarik ke pemroses sampai sistem mengirimkanACTION_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 dariView
pemroses.ACTION_DRAG_LOCATION
: Setelah menerima peristiwaACTION_DRAG_ENTERED
, dan sebelum menerima peristiwaACTION_DRAG_EXITED
, pemroses akan menerima peristiwaACTION_DRAG_LOCATION
baru setiap kali titik sentuh berpindah. MetodegetX()
dangetY()
menampilkan koordinat X dan Y titik kontak.ACTION_DRAG_EXITED
: Jenis tindakan peristiwa ini dikirim ke pemroses yang sebelumnya menerimaACTION_DRAG_ENTERED
. Peristiwa ini dikirim saat titik sentuh bayangan tarik berpindah dari dalam kotak pembatasView
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
atauACTION_DRAG_LOCATION
, pemroses dapat mengubah tampilanView
untuk menunjukkan bahwa tampilan adalah potensi target lepas. - Peristiwa dengan jenis tindakan
ACTION_DRAG_LOCATION
berisi data yang validgetX()
dangetY()
, yang bersesuaian dengan lokasi titik sentuh. Pemroses dapat menggunakan informasi ini untuk mengubah tampilanView
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 terhadapACTION_DRAG_ENTERED
atauACTION_DRAG_LOCATION
. Hal ini menunjukkan kepada pengguna bahwaView
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:
Memanggil
getClipData()
untuk mendapatkan objekClipData
yang awalnya disediakan dalam panggilan kestartDragAndDrop()
dan proses data.Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini tidak diperlukan.
Menampilkan boolean
true
yang mengindikasikan bahwa operasi lepas berhasil diproses, atau booleanfalse
jika operasi lepas tidak berhasil. Nilai yang ditampilkan menjadi nilai yang ditampilkan olehgetResult()
untuk peristiwaACTION_DRAG_ENDED
akhir.Perhatikan bahwa jika sistem tidak mengirimkan peristiwa
ACTION_DROP
, nilai yang ditampilkan olehgetResult()
untuk peristiwaACTION_DRAG_ENDED
adalahfalse
.
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:
- Jika pemroses mengubah tampilan objek
View
selama operasi, pemroses harus meresetView
ke tampilan default-nya. Ini adalah indikasi visual bagi pengguna bahwa operasi telah selesai. - Jika diinginkan, pemroses dapat memanggil
getResult()
untuk mengetahui operasi tersebut lebih lanjut. Jika pemroses menampilkantrue
sebagai respons terhadap peristiwa dengan jenis tindakanACTION_DROP
,getResult()
akan menampilkan booleantrue
. Dalam semua kasus lainnya,getResult()
akan menampilkan booleanfalse
, termasuk jika sistem tidak mengirim peristiwaACTION_DROP
. - 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
danDRAG_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 memanggilrelease()
pada objek yang ditampilkan darirequestDragAndDropPermissions()
. 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:
Activity
saat ini (digunakan untuk izin URI)View
yang berfungsi sebagai target lepas.- Jenis MIME yang dapat diterima target lepas dari data yang dilepas
- Opsi konfigurasi untuk target lepas (khususnya, daftar kolom EditText yang disematkan)
OnReceiveContentListener
untuk menangani data yang dilepas
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:
EditText
tempatClipData
dilepasEditText
yang berisi kursor teks (tanda sisipan)EditText
pertama yang diberikan ke panggilan untukDropHelper.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.