Framework tarik lalu lepas Android memungkinkan Anda menambahkan kemampuan tarik lalu lepas
yang 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
lainnya dalam aplikasi, atau antar-aplikasi
dalam mode multi-aplikasi.
![]() |
![]() |
|
|
Framework ini mencakup class peristiwa tarik, pemroses tarik, serta class dan metode helper. Meskipun terutama dirancang untuk memungkinkan transfer data, Anda dapat menggunakan framework untuk tindakan UI lainnya. Misalnya, Anda dapat membuat aplikasi yang menggabungkan warna saat pengguna menarik ikon warna ke ikon lain. Namun, bagian dokumen lainnya 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 merilis 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,
baik bayangan tarik berada di atas target lepas atau tidak.
Buat pemroses peristiwa tarik dengan mengimplementasikan
View.OnDragListener
. Tetapkan
pemroses untuk target operasi 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 menyediakan
data yang ditarik pengguna dan metadata yang menjelaskan data kepada sistem. Anda
dapat memanggil startDragAndDrop()
pada 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 melepas data pada target
lepas—View
yang menerima data—sistem akan mengirim objek peristiwa
tarik yang berisi data tersebut ke pemroses peristiwa tarik atau metode callback
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
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 untuk objek View
, panggil setOnDragListener()
.
Sebagai alternatif, Anda dapat mengubah implementasi default onDragEvent()
tanpa mengganti metode. Menyetel
OnReceiveContentListener
pada tampilan; untuk detail selengkapnya, lihat
setOnReceiveContentListener()
.
Kemudian, metode onDragEvent()
akan 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 objekContentInfo
. Metode ini memanggilOnReceiveContentListener
.Menampilkan true jika data tarik lalu lepas dilepaskan pada tampilan dan
OnReceiveContentListener
menggunakan konten mana pun.
Tentukan OnReceiveContentListener
untuk menangani data secara 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, dilepaskan, dan berakhir.
- Dimulai
Sebagai respons terhadap gestur tarik pengguna, aplikasi Anda 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 semua objekView
dalam tata letak saat ini. Untuk terus menerima peristiwa tarik, termasuk kemungkinan peristiwa lepas, pemroses peristiwa tarik harus menampilkantrue
. Tindakan ini akan mendaftarkan pemroses ke sistem. Hanya pemroses terdaftar yang akan terus menerima peristiwa tarik. Pada tahap ini, pemroses juga dapat mengubah tampilan objekView
target operasi lepas untuk menunjukkan bahwa tampilan dapat menerima peristiwa lepas. : Jika pemroses peristiwa tarik menampilkanfalse
, pemroses tidak akan menerima peristiwa tarik untuk operasi saat ini hingga sistem mengirimkan peristiwa tarik dengan jenis tindakanACTION_DRAG_ENDED
. 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 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
View
target lepas sebagai respons terhadap peristiwa tersebut. Misalnya, jika peristiwa tersebut menunjukkan bahwa bayangan tarik memasuki kotak pembatas target lepas—jenis tindakanACTION_DRAG_ENTERED
—pemroses dapat bereaksi dengan menandaiView
. - Dilepas
- Pengguna melepaskan 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. Pemroses diharapkan menampilkan booleantrue
ke sistem jika pemroses berhasil memproses data yang dilepas. : Langkah ini hanya terjadi jika pengguna melepas bayangan tarik di dalam kotak pembatasView
yang pemrosesnya terdaftar untuk menerima peristiwa tarik (target lepas). Jika pengguna melepas bayangan tarik dalam situasi lain, tidak ada peristiwa tarikACTION_DROP
yang dikirim. - Berakhir
Setelah pengguna melepas bayangan tarik, dan setelah sistem mengirim
keluar dari peristiwa tarik dengan jenis tindakan
ACTION_DROP
, jika perlu, sistem akan mengirimkan peristiwa tarik dengan jenis tindakanACTION_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 peristiwaACTION_DROP
.
Setiap langkah ini dijelaskan secara lebih mendetail di bagian yang disebut 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 di class DragEvent
,
yang dijelaskan dalam tabel 1:
Tabel 1. Jenis tindakan DragEvent
Jenis tindakan | Arti |
---|---|
ACTION_DRAG_STARTED |
Aplikasi memanggil startDragAndDrop() dan 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 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 melepas bayangan
tarik di atas apa pun yang bukan bagian dari tata letak saat ini.
Pemroses menampilkan boolean |
ACTION_DRAG_ENDED |
Sistem mengakhiri operasi tarik lalu lepas. Jenis tindakan ini
tidak harus didahului dengan peristiwa ACTION_DROP . Jika
sistem mengirim ACTION_DROP , penerimaan jenis tindakan
ACTION_DRAG_ENDED tidak berarti bahwa
penurunan berhasil. Pemroses harus memanggil
getResult() ,
seperti yang ditunjukkan dalam 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 tersebut
hanya valid untuk jenis tindakan tertentu seperti yang dirangkum dalam tabel 2. Untuk mengetahui informasi
selengkapnya tentang peristiwa dan data terkait, 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
yaitu 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 memulai operasi tarik lalu lepas
menggunakan startDragAndDrop()
. Sebagai bagian dari respons terhadap
startDragAndDrop()
, sistem 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 objekView
dalam objekView.DragShadowBuilder
, sehingga callback dapat mengaksesnya untuk mengonstruksi bayangan tarik. Tampilan tidak harus berupaView
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 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 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:outShadowSize
: objekPoint
. Lebar bayangan tarik masuk dix
, dan tingginya masuk diy
.outShadowTouchPoint
: objekPoint
. Titik sentuh adalah lokasi dalam bayangan tarik yang harus berada di bawah jari pengguna selama operasi tarik. Posisi X ada dix
dan posisi Y ada diy
.onDrawShadow()
Segera setelah panggilan ke
onProvideShadowMetrics()
, sistem akan memanggilonDrawShadow()
untuk membuat bayangan tarik. Metode ini memiliki satu argumen, objekCanvas
yang dibentuk oleh sistem dari parameter yang Anda berikan dionProvideShadowMetrics()
. Metode ini menggambar bayangan tarik padaCanvas
yang disediakan.
Untuk meningkatkan performa, pertahankan ukuran bayangan tarik tetap kecil. Untuk satu item, Anda mungkin perlu menggunakan ikon. Untuk pemilihan beberapa item, 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 tindakan tarik, merespons peristiwa selama tarik, merespons peristiwa lepas, dan mengakhiri operasi tarik lalu lepas.
Mulai operasi tarik
Pengguna memulai operasi tarik dengan gestur tarik, biasanya sentuh lama, pada 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
, bukan 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(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; });
Tentukan
myDragShadowBuilder
dengan mengganti metode diView.DragShadowBuilder
. Cuplikan kode berikut membuat bayangan tarik persegi panjang dan abu-abu kecil untukTextView
: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 operasi tarik,
metode ini akan 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 dalamClipDescription
yang ditampilkan untuk melihat apakah pemroses dapat menerima data yang ditarik.Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, hal ini mungkin tidak diperlukan.
Jika pemroses peristiwa tarik dapat menerima peristiwa lepas, pemroses harus menampilkan
true
untuk memberi tahu sistem agar terus mengirim peristiwa tarik ke pemroses. Jika pemroses tidak dapat menerima peristiwa lepas, pemroses harus menampilkanfalse
, dan sistem akan berhenti mengirim peristiwa tarik ke pemroses hingga sistem mengirimkanACTION_DRAG_ENDED
untuk menyelesaikan operasi tarik lalu lepas.
Untuk peristiwa ACTION_DRAG_STARTED
, metode DragEvent
berikut tidak
valid: getClipData()
,
getX()
,
getY()
, dan
getResult()
.
Menangani peristiwa selama operasi tarik
Selama tindakan tarik, pemroses peristiwa tarik yang menampilkan true
sebagai respons terhadap
peristiwa tarik ACTION_DRAG_STARTED
akan terus menerima peristiwa tarik. Jenis
peristiwa tarik yang diterima pemroses selama operasi tarik bergantung pada lokasi
bayangan tarik dan visibilitas View
pemroses. Pemroses menggunakan peristiwa
tarik terutama untuk menentukan 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—memasuki kotak pembatasView
pemroses.ACTION_DRAG_LOCATION
: setelah menerima peristiwaACTION_DRAG_ENTERED
, pemroses akan menerima peristiwaACTION_DRAG_LOCATION
baru setiap kali titik sentuh bergerak hingga menerima peristiwaACTION_DRAG_EXITED
. MetodegetX()
dangetY()
menampilkan koordinat X dan Y titik sentuh.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 pemroses menampilkan nilai ke sistem, nilai tersebut 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 valid untukgetX()
dangetY()
yang sesuai dengan lokasi titik sentuh. Pemroses dapat menggunakan informasi ini untuk mengubah tampilanView
pada titik sentuh atau menentukan posisi persis tempat pengguna dapat melepaskan bayangan tarik—yaitu, melepaskan data. - Sebagai respons terhadap
ACTION_DRAG_EXITED
, pemroses harus mereset setiap perubahan tampilan 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
Saat pengguna merilis bayangan tarik di atas View
, dan View
sebelumnya
melaporkan bahwa pengguna dapat menerima konten yang sedang ditarik, sistem akan mengirim
peristiwa tarik ke View
dengan jenis tindakan ACTION_DROP
.
Pemroses peristiwa tarik harus melakukan hal berikut:
Panggil
getClipData()
untuk mendapatkan objekClipData
yang awalnya disediakan dalam panggilan kestartDragAndDrop()
dan proses data. Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, hal ini tidak diperlukan.Menampilkan boolean
true
untuk menunjukkan bahwa operasi lepas berhasil diproses, ataufalse
jika tidak berhasil. Nilai yang ditampilkan menjadi nilai yang ditampilkan olehgetResult()
untuk peristiwaACTION_DRAG_ENDED
akhir. Jika sistem tidak mengirimkan peristiwaACTION_DROP
, nilai yang ditampilkan olehgetResult()
untuk peristiwaACTION_DRAG_ENDED
adalahfalse
.
Untuk peristiwa ACTION_DROP
, getX()
dan getY()
menggunakan sistem koordinat
View
yang menerima penurunan untuk menampilkan posisi X dan Y titik sentuh
pada saat penurunan terjadi.
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 di 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 tidak mengirim peristiwa ACTION_DRAG_ENDED
.
Merespons akhir operasi tarik
Segera setelah pengguna melepas bayangan tarik, sistem akan mengirimkan peristiwa
tarik dengan jenis tindakan ACTION_DRAG_ENDED
ke semua pemroses peristiwa tarik
dalam aplikasi Anda. Hal ini menunjukkan bahwa operasi tarik lalu lepas telah selesai.
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 berjenis tindakanACTION_DROP
,getResult()
akan menampilkan booleantrue
. Dalam semua kasus lainnya,getResult()
akan menampilkan booleanfalse
, termasuk saat 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 -> // 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, 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 berbagi akses ke data menggunakan URI konten. Hal ini memerlukan hal berikut:
- Aplikasi sumber harus menetapkan salah satu atau kedua tanda
DRAG_FLAG_GLOBAL_URI_READ
danDRAG_FLAG_GLOBAL_URI_WRITE
, 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 dapat memanggilrelease()
pada objek yang ditampilkan darirequestDragAndDropPermissions()
. Jika tidak, izin akan dirilis saat aktivitas yang memuatnya dihancurkan. Jika implementasi Anda melibatkan memulai Aktivitas baru untuk memproses URI yang dihapus, Anda perlu memberikan izin yang sama kepada 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:
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 operasi 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 operasi 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 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:
EditText
tempatClipData
dilepas.EditText
yang berisi kursor teks (tanda sisipan).EditText
pertama yang diberikan ke panggilan keDropHelper.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 disediakan 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 tinggi.
AppCompatEditText
, jika pengguna menjalankan versi Android hingga Android 7.0.
Jenis MIME, izin, dan validasi konten
Pemeriksaan jenis MIME oleh DropHelper
didasarkan pada tarik lalu lepas
ClipDescription
, yang
dibuat oleh aplikasi yang menyediakan data tarik lalu lepas. Validasi
ClipDescription
untuk memastikan jenis MIME ditetapkan dengan benar.
DropHelper
meminta semua izin akses untuk URI konten yang terdapat dalam
tarik lalu lepas ClipData
. Untuk mengetahui informasi selengkapnya, lihat
DragAndDropPermissions
. Izin
tersebut 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 kebenaran
data yang diselesaikan.