Dengan framework tarik/lepas Android, Anda dapat mengizinkan pengguna memindahkan data dari satu View ke View lainnya menggunakan gestur grafis tarik lalu lepas. Framework ini mencakup class peristiwa tarik, pemroses tarik, serta class dan metode helper.
Meskipun framework ini utamanya didesain untuk pemindahan data, Anda dapat menggunakannya 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 topik ini akan menjelaskan framework dari segi pemindahan data.
Anda juga sebaiknya melihat referensi terkait berikut ini:
Ringkasan
Operasi tarik lalu lepas dimulai saat pengguna membuat gestur yang Anda kenali sebagai
sinyal untuk mulai menarik data. Sebagai respons, aplikasi Anda memberi tahu sistem bahwa operasi tarik akan
dimulai. Sistem melakukan callback ke aplikasi Anda untuk mendapatkan representasi dari data
yang ditarik. Saat jari pengguna memindahkan representasi ini ("bayangan tarik")
ke atas tata letak saat ini, sistem akan mengirimkan peristiwa tarik ke objek pemroses dan
metode callback peristiwa tarik yang terkait dengan objek View
dalam tata letak itu.
Setelah pengguna melepas bayangan tarik, sistem akan mengakhiri operasi tarik tersebut.
Objek pemroses peristiwa tarik ("pemroses") dibuat dari class yang mengimplementasikan
View.OnDragListener
. Objek pemroses peristiwa tarik untuk sebuah View ditetapkan
dengan metode
setOnDragListener()
objek View tersebut.
Setiap objek View juga memiliki
metode callback onDragEvent()
. Keduanya dijelaskan secara lebih mendetail di bagian
Pemroses dan metode callback peristiwa tarik.
Catatan: Agar lebih praktis, bagian-bagian di bawah menyebut rutinitas yang menerima peristiwa tarik sebagai "pemroses peristiwa tarik", meskipun sebenarnya itu adalah metode callback.
Saat memulai operasi tarik, Anda menyertakan data yang Anda pindahkan dan metadata yang menjelaskan data ini sebagai bagian dari panggilan ke sistem. Selama operasi tarik, sistem akan mengirim peristiwa tarik ke pemroses atau metode callback peristiwa tarik dari setiap View dalam tata letak. Pemroses atau metode callback dapat menggunakan metadata ini untuk memutuskan apakah ingin menerima data saat data tersebut dilepas. Jika pengguna melepas data di atas sebuah objek View, dan pemroses atau metode callback objek View itu telah memberi tahu sistem bahwa ia ingin menerima data yang dilepas tersebut, sistem akan mengirimkan data itu ke pemroses atau metode callback dalam peristiwa tarik.
Aplikasi Anda meminta sistem untuk memulai operasi tarik dengan memanggil
metode
startDrag()
. Tindakan ini akan memberi tahu sistem untuk mulai mengirimkan peristiwa tarik. Metode ini juga mengirimkan data yang
sedang Anda tarik.
Anda dapat memanggil
startDrag()
untuk View terkait apa pun dalam tata letak saat ini. Sistem hanya menggunakan objek View untuk mendapatkan akses
ke setelan global dalam tata letak Anda.
Setelah aplikasi Anda memanggil
startDrag()
,
proses selanjutnya akan menggunakan peristiwa yang dikirim oleh sistem ke objek View dalam tata letak Anda
saat ini.
Catatan: Jika aplikasi berjalan dalam mode multi-aplikasi, pengguna dapat menarik lalu melepaskan data dari satu aplikasi ke aplikasi lainnya. Untuk informasi selengkapnya, lihat Mendukung operasi tarik lalu lepas.
Proses tarik/lepas
Pada dasarnya, ada empat langkah atau status dalam proses tarik lalu lepas:
- Dimulai
-
Sebagai respons terhadap gestur pengguna untuk memulai proses tarik, aplikasi Anda akan memanggil
startDrag()
untuk meminta sistem memulai proses tarik. ArgumenstartDrag()
menyediakan data yang akan ditarik, metadata untuk data ini, dan callback untuk menggambar bayangan tarik.Sistem terlebih dahulu akan merespons dengan melakukan callback ke aplikasi Anda untuk mendapatkan bayangan tarik. Kemudian, sistem akan menampilkan bayangan tarik di perangkat.
Selanjutnya, sistem mengirimkan peristiwa tarik dengan jenis tindakan
ACTION_DRAG_STARTED
ke pemroses peristiwa tarik untuk semua objek View dalam tata letak saat ini. Untuk terus menerima peristiwa tarik, termasuk kemungkinan peristiwa lepas, pemroses peristiwa tarik harus menampilkantrue
. Tindakan ini mendaftarkan pemroses ke sistem. Hanya pemroses terdaftar yang akan terus menerima peristiwa tarik. Pada tahap ini, pemroses juga dapat mengubah tampilan objek View-nya untuk menunjukkan bahwa pemroses dapat menerima peristiwa lepas.Jika pemroses peristiwa tarik menampilkan
false
, maka ia tidak akan menerima peristiwa tarik untuk operasi saat ini hingga sistem mengirimkan peristiwa tarik dengan jenis tindakanACTION_DRAG_ENDED
. Dengan mengirimkanfalse
, pemroses memberi tahu sistem bahwa ia tidak tertarik dengan operasi tarik itu dan tidak ingin menerima data yang ditarik. - Melanjutkan
-
Pengguna melanjutkan proses tarik. Saat bayangan tarik memotong kotak pembatas dari sebuah objek View,
sistem akan mengirimkan satu atau beberapa peristiwa tarik ke pemroses peristiwa tarik untuk objek View
tersebut (jika terdaftar untuk menerima peristiwa). Pemroses dapat memilih untuk
mengubah tampilan objek View-nya sebagai respons terhadap peristiwa tersebut. Misalnya, jika peristiwa itu
mengindikasikan bahwa bayangan tarik telah memasuki kotak pembatas View
(jenis tindakan
ACTION_DRAG_ENTERED
), pemroses dapat bereaksi dengan menyorot View-nya. - Dilepas
-
Pengguna melepas bayangan tarik di dalam kotak pembatas View yang dapat menerima
data. Sistem mengirimkan peristiwa tarik ke pemroses objek View dengan jenis tindakan
ACTION_DROP
. Peristiwa tarik berisi data yang diteruskan ke sistem dalam panggilan kestartDrag()
yang memulai operasi ini. Pemroses diharapkan menampilkan booleantrue
ke sistem jika kode untuk menerima peristiwa lepas berhasil.Perhatikan bahwa langkah ini hanya terjadi jika pengguna melepas bayangan tarik di dalam kotak pembatas View yang pemrosesnya terdaftar untuk menerima peristiwa tarik. Jika pengguna melepas bayangan tarik dalam situasi lain, tidak ada peristiwa tarik
ACTION_DROP
yang dikirim. - Selesai
-
Setelah pengguna melepas bayangan tarik, dan setelah sistem mengirim (jika perlu)
peristiwa tarik dengan jenis tindakan
ACTION_DROP
, sistem akan mengirimkan peristiwa tarik dengan jenis tindakanACTION_DRAG_ENDED
untuk menunjukkan bahwa operasi tarik telah selesai. Hal ini dilakukan di mana pun pengguna melepas 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 di bagian Mendesain Operasi Tarik lalu Lepas.
Pemroses dan metode callback peristiwa tarik
View menerima peristiwa tarik dengan pemroses peristiwa tarik yang mengimplementasikan
View.OnDragListener
, atau dengan metode callback
onDragEvent(DragEvent)
-nya.
Saat memanggil metode atau pemroses, sistem akan meneruskan objek
DragEvent
ke metode atau pemroses tersebut.
Dalam sebagian besar kasus, sebaiknya Anda menggunakan pemroses. Saat mendesain UI, biasanya
Anda tidak membuat subclass dari class View, tetapi penggunaan metode callback akan memaksa Anda melakukan hal ini agar dapat
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. Untuk
menetapkan pemroses bagi objek View, panggil
setOnDragListener()
.
Anda dapat menggunakan pemroses dan juga metode callback untuk objek View. Jika ini terjadi,
sistem akan memanggil pemroses terlebih dahulu. Sistem tidak memanggil metode callback kecuali jika
pemroses menampilkan false
.
Gabungan dari metode onDragEvent(DragEvent)
dan
View.OnDragListener
serupa dengan gabungan
dari onTouchEvent()
dan
View.OnTouchListener
yang digunakan dengan peristiwa sentuh.
Peristiwa tarik
Sistem mengirim peristiwa tarik dalam bentuk objek DragEvent
.
Objek ini berisi jenis tindakan yang memberi tahu pemroses apa yang terjadi dalam proses
tarik/lepas. Objek ini berisi data lain, bergantung pada jenis tindakannya.
Untuk mendapatkan jenis tindakan, pemroses akan memanggil getAction()
. Ada
enam kemungkinan nilai, yang ditentukan oleh konstanta dalam class DragEvent
. Nilai ini
tercantum dalam tabel 1.
Objek DragEvent
juga berisi data yang diberikan oleh aplikasi Anda
ke sistem dalam panggilan ke
startDrag()
.
Sebagian data ini hanya dapat digunakan untuk jenis tindakan tertentu. Data yang dapat digunakan untuk setiap jenis tindakan
dirangkum dalam tabel 2. Hal ini juga dijelaskan secara mendetail beserta
peristiwa yang dapat menggunakan data tersebut di bagian
Mendesain Operasi Tarik lalu Lepas.
Tabel 1. Jenis tindakan DragEvent
Nilai getAction() | Arti |
---|---|
ACTION_DRAG_STARTED |
Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini tepat setelah
aplikasi memanggil
startDrag() dan
mendapatkan bayangan tarik.
|
ACTION_DRAG_ENTERED |
Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini saat bayangan tarik
baru saja memasuki kotak pembatas View. Ini adalah jenis tindakan peristiwa pertama yang
diterima pemroses saat bayangan tarik memasuki kotak pembatas. Jika pemroses ingin terus menerima peristiwa tarik untuk
operasi ini, pemroses harus menampilkan boolean
true ke sistem.
|
ACTION_DRAG_LOCATION |
Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini setelah menerima peristiwa
ACTION_DRAG_ENTERED sementara bayangan tarik
masih berada dalam kotak pembatas View.
|
ACTION_DRAG_EXITED |
Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini setelah menerima
ACTION_DRAG_ENTERED dan minimal satu peristiwa
ACTION_DRAG_LOCATION , dan setelah pengguna memindahkan
bayangan tarik ke luar kotak pembatas View.
|
ACTION_DROP |
Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini saat pengguna
melepas bayangan tarik di atas objek View. Jenis tindakan ini hanya dikirim ke pemroses
objek View jika pemroses menampilkan boolean true sebagai respons terhadap
peristiwa tarik ACTION_DRAG_STARTED . Jenis tindakan ini tidak
dikirim jika pengguna melepas 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 diharapkan menampilkan boolean |
ACTION_DRAG_ENDED |
Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini
saat sistem mengakhiri operasi tarik. Jenis tindakan ini tidak perlu
didahului oleh peristiwa ACTION_DROP . Jika sistem mengirim
ACTION_DROP , penerimaan jenis tindakan
ACTION_DRAG_ENDED tidak berarti bahwa
operasi lepas berhasil. Pemroses harus memanggil
getResult() untuk mendapatkan nilai yang
ditampilkan sebagai respons terhadap ACTION_DROP . Jika peristiwa
ACTION_DROP tidak terkirim, maka
getResult() akan menampilkan false .
|
Tabel 2. Data DragEvent yang valid menurut jenis tindakan
Nilai getAction() |
NIlai getClipDescription() |
NIlai getLocalState() |
NIlai getX() |
NIlai getY() |
NIlai getClipData() |
Nilai getResult() |
---|---|---|---|---|---|---|
ACTION_DRAG_STARTED |
X | X | X | |||
ACTION_DRAG_ENTERED |
X | X | X | X | ||
ACTION_DRAG_LOCATION |
X | X | X | X | ||
ACTION_DRAG_EXITED |
X | X | ||||
ACTION_DROP |
X | X | X | X | X | |
ACTION_DRAG_ENDED |
X | X | X |
Metode 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. Bayangan tarik dibuat dengan metode yang Anda deklarasikan untuk
objek View.DragShadowBuilder
, lalu diteruskan ke sistem saat Anda
memulai peristiwa tarik menggunakan
startDrag()
.
Sebagai bagian dari respons terhadap
startDrag()
,
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 objekView.DragShadowBuilder
, sehingga selama callback, Anda dapat mengaksesnya saat membuat bayangan tarik. Konstruktor ini tidak harus dikaitkan dengan 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 di
objek
View.DragShadowBuilder
(kolom ini ditetapkan kenull
). Jika menggunakan konstruktor ini, dan tidak memperluasView.DragShadowBuilder
atau mengganti metodenya, Anda akan mendapatkan bayangan tarik yang tak terlihat. Sistem tidak menampilkan error.
Class View.DragShadowBuilder
memiliki dua metode:
-
onProvideShadowMetrics()
-
Sistem akan memanggil metode ini segera setelah Anda memanggil
startDrag()
. Gunakan metode ini untuk mengirimkan dimensi dan titik sentuh bayangan tarik ke sistem. Metode ini memiliki dua argumen: -
onDrawShadow()
-
Segera setelah panggilan ke
onProvideShadowMetrics()
sistem akan memanggilonDrawShadow()
untuk mendapatkan bayangan tarik itu sendiri. Metode ini memiliki argumen tunggal, objekCanvas
yang dibuat oleh sistem dari parameter yang Anda masukkan dalamonProvideShadowMetrics()
Gunakan argumen ini untuk menggambar bayangan tarik dalam objekCanvas
yang disediakan.
Untuk meningkatkan performa, sebaiknya ukuran bayangan tarik dipertahankan sekecil mungkin. Untuk satu item, sebaiknya Anda menggunakan ikon. Untuk beberapa pilihan, Anda dapat menggunakan ikon dalam stack, bukan gambar penuh yang menyebar ke layar.
Mendesain 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.
Memulai operasi tarik
Pengguna memulai operasi tarik dengan gestur menarik, biasanya dengan menekan lama objek View. Sebagai respons, Anda harus melakukan hal berikut:
-
Jika diperlukan, buat
ClipData
danClipData.Item
untuk data yang sedang dipindahkan. Sebagai bagian dari objek ClipData, berikan metadata yang tersimpan di objekClipDescription
dalam ClipData. Untuk operasi tarik lalu lepas yang tidak merepresentasikan perpindahan data Anda dapat menggunakannull
alih-alih objek yang sebenarnya.Misalnya, cuplikan kode ini menunjukkan cara merespons gestur tekan lama pada ImageView dengan membuat objek ClipData yang berisi tag atau label sebuah ImageView. Setelah cuplikan ini, cuplikan berikutnya menunjukkan cara mengganti metode dalam
View.DragShadowBuilder
:Kotlin
const val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(this).apply { setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG imageView.setOnLongClickListener { v: View -> // 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 will create a new ClipDescription object within the // ClipData, and set its MIME type entry to "text/plain" val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiates the drag shadow builder. val myShadow = MyDragShadowBuilder(this) // Starts the drag v.startDrag( 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) ) } }
Java
// Create a string for the ImageView label private static final String IMAGEVIEW_TAG = "icon bitmap" // Creates a new ImageView ImageView imageView = new ImageView(this); // Sets the bitmap for the ImageView from an icon bit map (defined elsewhere) imageView.setImageBitmap(iconBitmap); // Sets 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(new View.OnLongClickListener() { // Defines the one method for the interface, which is called when the View is long-clicked public boolean onLongClick(View 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(v.getTag()); // Create a new ClipData using the tag as a label, the plain text MIME type, and // the already-created item. This will create a new ClipDescription object within the // ClipData, and set its MIME type entry to "text/plain" ClipData dragData = new ClipData( v.getTag(), new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item); // Instantiates the drag shadow builder. View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView); // Starts the drag v.startDrag(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) ); } }
-
Cuplikan kode berikut menentukan
myDragShadowBuilder
Cuplikan ini menghasilkan bayangan tarik untuk menarik TextView sebagai kotak kecil berwarna abu-abu: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) { // Sets the width of the shadow to half the width of the original View val width: Int = view.width / 2 // Sets 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 will provide. As a result, the drag shadow will fill the // Canvas. shadow.setBounds(0, 0, width, height) // Sets the size parameter's width and height values. These get back to the system // through the size parameter. size.set(width, height) // Sets 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 in onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draws the ColorDrawable in 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 thing private static Drawable shadow; // Defines the constructor for myDragShadowBuilder public MyDragShadowBuilder(View v) { // Stores the View parameter passed to myDragShadowBuilder. super(v); // Creates a draggable image that will fill 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 private int width, height; // Sets the width of the shadow to half the width of the original View width = getView().getWidth() / 2; // Sets 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 will provide. As a result, the drag shadow will fill the // Canvas. shadow.setBounds(0, 0, width, height); // Sets the size parameter's width and height values. These get back to the system // through the size parameter. size.set(width, height); // Sets 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 in onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draws the ColorDrawable in the Canvas passed in from the system. shadow.draw(canvas); } }
Catatan: Ingatlah bahwa Anda tidak perlu memperluas
View.DragShadowBuilder
. KonstruktorView.DragShadowBuilder(View)
menghasilkan bayangan tarik default yang ukurannya sama dengan argumen View yang diteruskan ke konstruktor tersebut, dengan titik sentuh yang berada di tengah-tengah bayangan tarik.
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 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 harus melakukan tindakan berikut:
-
Memanggil
getClipDescription()
untuk mendapatkanClipDescription
. Gunakan metode jenis MIME diClipDescription
untuk melihat apakah pemroses dapat menerima data yang sedang ditarik.Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini mungkin tidak diperlukan.
-
Jika dapat menerima operasi lepas, pemroses harus menampilkan
true
. Tindakan ini akan memberi tahu sistem untuk terus mengirim peristiwa tarik ke pemroses. Jika tidak dapat menerima peristiwa lepas, pemroses harus menampilkanfalse
, dan sistem akan berhenti mengirimkan peristiwa tarik hinggaACTION_DRAG_ENDED
terkirim.
Perhatikan bahwa untuk peristiwa ACTION_DRAG_STARTED
, metode
DragEvent
berikut tidak valid:
getClipData()
, getX()
,
getY()
, dan getResult()
.
Menangani peristiwa selama operasi tarik
Selama proses tarik, pemroses 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.
Selama operasi tarik, biasanya pemroses menggunakan peristiwa tarik untuk memutuskan apakah perlu mengubah tampilan View-nya atau tidak.
Selama operasi tarik, getAction()
menampilkan satu dari tiga
nilai berikut:
-
ACTION_DRAG_ENTERED
: Pemroses menerima ini jika titik sentuh (titik pada layar di bawah jari pengguna) telah memasuki kotak pembatas View 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 sentuh. -
ACTION_DRAG_EXITED
: Peristiwa ini dikirim ke pemroses yang sebelumnya menerimaACTION_DRAG_ENTERED
, setelah bayangan tarik tidak lagi berada dalam kotak pembatas View pemroses.
Pemroses tidak perlu bereaksi terhadap salah satu jenis tindakan ini. Jika pemroses menampilkan nilai ke sistem, nilai tersebut akan diabaikan. Berikut adalah beberapa panduan untuk merespons setiap jenis tindakan:
-
Sebagai respons terhadap
ACTION_DRAG_ENTERED
atauACTION_DRAG_LOCATION
, pemroses dapat mengubah tampilan View untuk mengindikasikan bahwa ia akan segera menerima operasi lepas. -
Peristiwa dengan jenis tindakan
ACTION_DRAG_LOCATION
berisi data yang valid untukgetX()
dangetY()
, yang bersesuaian dengan lokasi titik sentuh. Pemroses dapat menggunakan informasi ini untuk mengubah tampilan bagian tersebut dari View yang berada di titik sentuh. Pemroses juga dapat menggunakan informasi ini untuk menentukan posisi persis di mana pengguna akan melepas bayangan tarik. -
Sebagai respons terhadap
ACTION_DRAG_EXITED
, pemroses perlu menyetel ulang perubahan tampilan apa pun yang diterapkannya sebagai respons terhadapACTION_DRAG_ENTERED
atauACTION_DRAG_LOCATION
. Hal ini menunjukkan kepada pengguna bahwa View tersebut tidak lagi menjadi target operasi lepas yang akan segera terjadi.
Merespons operasi lepas
Jika pengguna melepas bayangan tarik di atas sebuah View dalam aplikasi, dan View itu sebelumnya
melaporkan bahwa ia dapat menerima konten yang sedang ditarik, maka sistem akan mengirimkan peristiwa tarik
ke View tersebut dengan jenis tindakan ACTION_DROP
. Pemroses
harus melakukan hal berikut:
-
Memanggil
getClipData()
untuk mendapatkan objekClipData
yang awalnya disediakan dalam panggilan kestartDrag()
dan menyimpannya. Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini mungkin tidak diperlukan. -
Menampilkan boolean
true
untuk 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
.Perhatikan bahwa jika sistem tidak mengirimkan peristiwa
ACTION_DROP
, nilaigetResult()
untuk peristiwaACTION_DRAG_ENDED
adalahfalse
.
Untuk peristiwa ACTION_DROP
,
getX()
dan getY()
menampilkan posisi X dan Y titik tarik pada saat peristiwa lepas, menggunakan sistem
koordinat View yang menerima operasi lepas.
Sistem memungkinkan pengguna melepas bayangan tarik di atas View yang pemrosesnya tidak
menerima peristiwa tarik. Sistem juga memungkinkan pengguna melepas bayangan tarik
di area kosong dalam UI aplikasi, atau area di luar aplikasi Anda.
Dalam semua kasus ini, sistem tidak mengirimkan peristiwa dengan jenis tindakan
ACTION_DROP
, tetapi tetap mengirimkan
peristiwa ACTION_DRAG_ENDED
.
Merespons akhir operasi tarik
Segera setelah pengguna melepas bayangan tarik, sistem akan mengirimkan
peristiwa tarik ke semua pemroses peristiwa tarik dalam aplikasi Anda, dengan jenis tindakan
ACTION_DRAG_ENDED
. Ini menandakan bahwa operasi tarik telah
selesai.
Setiap pemroses harus melakukan berikut ini:
- Jika sudah mengubah tampilan objek View-nya selama operasi, pemroses harus menyetel ulang View 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
, makagetResult()
akan menampilkan booleantrue
. Dalam semua kasus lainnya,getResult()
akan menampilkan booleanfalse
, termasuk kasus apa pun ketika sistem tidak mengirimkan peristiwaACTION_DROP
. -
Pemroses harus menampilkan boolean
true
ke sistem.
Merespons peristiwa tarik: contoh
Semua peristiwa tarik awalnya diterima oleh metode atau pemroses peristiwa tarik. Cuplikan kode berikut adalah contoh sederhana dari bereaksi terhadap peristiwa tarik di sebuah pemroses:
Kotlin
// Creates a new drag event listener private val dragListen = View.OnDragListener { v, event -> // Handles each of the expected events when (event.action) { DragEvent.ACTION_DRAG_STARTED -> { // Determines if this View can accept the dragged data if (event.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. 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. Return true; the return value is ignored. (v as? ImageView)?.setColorFilter(Color.GREEN) // Invalidate the view to force a redraw in the new tint v.invalidate() true } DragEvent.ACTION_DRAG_LOCATION -> // Ignore the event true DragEvent.ACTION_DRAG_EXITED -> { // Re-sets the color tint to blue. Returns true; the return value is ignored. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint v.invalidate() true } DragEvent.ACTION_DROP -> { // Gets the item containing the dragged data val item: ClipData.Item = event.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(event.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 OnDragListener.") false } } } ... val imageView = ImageView(this) // Sets the drag event listener for the View imageView.setOnDragListener(dragListen)
Java
// Creates a new drag event listener dragListen = new myDragEventListener(); View imageView = new ImageView(this); // Sets the drag event listener for the View imageView.setOnDragListener(dragListen); ... protected class myDragEventListener implements View.OnDragListener { // This is the method that the system calls when it dispatches a drag event to the // listener. public boolean onDrag(View v, DragEvent event) { // Defines a variable to store the action type for the incoming event final int action = event.getAction(); // Handles each of the expected events switch(action) { case DragEvent.ACTION_DRAG_STARTED: // Determines if this View can accept the dragged data if (event.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. 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. 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. Return true; the return value is ignored. v.setColorFilter(Color.GREEN); // Invalidate the view to force a redraw in the new tint v.invalidate(); return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event return true; case DragEvent.ACTION_DRAG_EXITED: // Re-sets the color tint to blue. Returns true; the return value is ignored. v.setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint v.invalidate(); return true; case DragEvent.ACTION_DROP: // Gets the item containing the dragged data ClipData.Item item = event.getClipData().getItemAt(0); // Gets the text data from the item. 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 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 v.clearColorFilter(); // Invalidates the view to force a redraw v.invalidate(); // Does a getResult(), and displays what happened. if (event.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 OnDragListener."); break; } return false; } };
Izin tarik dalam mode multi-aplikasi
Perangkat yang menjalankan Android 7.0 (API level 24) atau yang lebih baru mendukung mode multi-aplikasi, sehingga memungkinkan pengguna memindahkan data dari satu aplikasi ke aplikasi lain menggunakan operasi tarik lalu lepas:
- Aplikasi sumber: Aplikasi yang awalnya berisi data. Di situlah operasi tarik dimulai.
- Aplikasi target: Aplikasi yang menerima data. Di situlah operasi tarik berakhir.
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 dua-duanya, yang bergantung pada akses baca/tulis ke data yang seharusnya dimiliki oleh 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, aplikasi tersebut dapat memanggilrelease()
pada objek yang ditampilkan darirequestDragAndDropPermissions()
. Jika tidak, izin akan dilepas saat aktivitas yang memuatnya diakhiri.
Cuplikan kode berikut menunjukkan cara melepas akses baca saja ke data tarik segera setelah operasi tarik lalu lepas berlangsung. Contoh yang lebih lengkap dapat dilihat dalam sampel DragAndDropAcrossApps , yang tersedia di GitHub.
SourceDragAndDropActivity
Kotlin
// Drag a file stored under an "images/" directory within internal storage. val internalImagesDir = File(context.filesDir, "images") val imageFile = File(internalImagesDir, file-name) val uri: Uri = FileProvider.getUriForFile( context, file-provider-content-authority, imageFile) // Container for where the image originally appears in the source app. val srcImageView = findViewById(R.id.my-image-id) val listener = DragStartHelper.OnDragStartListener = { view, _ -> val clipData = ClipData(clip-description, 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, drag-shadow-builder, null, flags) } // Detect and start the drag event. DragStartHelper(srcImageView, listener).apply { attach() }
Java
// Drag a file stored under an "images/" directory within internal storage. File internalImagesDir = new File(context.filesDir, "images"); File imageFile = new File(internalImagesDir, file-name); final Uri uri = FileProvider.getUriForFile( context, file-provider-content-authority, imageFile); // Container for where the image originally appears in the source app. ImageView srcImageView = findViewById(R.id.my-image-id); DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener() { @Override public boolean onDragStart(View v, DragStartHelper helper) { ClipData clipData = new ClipData( clip-description, 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 v.startDragAndDrop(clipData, drag-shadow-builder, null, flags); } }; // Detect and start the drag event. DragStartHelper helper = new DragStartHelper(srcImageView, listener); helper.attach();
TargetDragAndDropActivity
Kotlin
// Container for where the image is to be dropped in the target app. val targetImageView = findViewById<ImageView>(R.id.my-image-id) 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. } }
Java
// Container for where the image is to be dropped in the target app. ImageView targetImageView = findViewById(R.id.my-image-id); targetImageView.setOnDragListener( new View.OnDragListener() { @Override public boolean onDrag(View view, DragEvent dragEvent) { switch (dragEvent.getAction()) { case ACTION_DROP: ClipData.Item imageItem = dragEvent.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(dragEvent); ((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. } } });