Anda dapat menerapkan proses tarik lalu lepas dalam tampilan dengan merespons peristiwa yang mungkin memicu awal tarik, serta merespons peristiwa lepas.
Mulai operasi tarik
Pengguna memulai tarik dengan gestur, biasanya dengan menyentuh atau mengklik lama item yang ingin ditarik.
Untuk menangani hal ini di View
, buat
objek ClipData
dan
objek ClipData.Item
untuk
data yang sedang dipindahkan. Sebagai bagian dari ClipData
, berikan metadata yang
disimpan dalam
objek ClipDescription
dalam ClipData
. Untuk operasi tarik lalu lepas yang tidak merepresentasikan
perpindahan data, Anda dapat menggunakan null
, bukan objek yang sebenarnya.
Misalnya, cuplikan kode ini menunjukkan cara merespons gestur sentuh lama
di ImageView
dengan membuat objek ClipData
yang berisi
tag (atau label) ImageView
.
Kotlin
// Create a string for the ImageView label. val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(context).apply { // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG setOnLongClickListener { v -> // Create a new ClipData. This is done in two steps to provide // clarity. The convenience method ClipData.newPlainText() can // create a plain text ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. val item = ClipData.Item(v.tag as? CharSequence) // Create a new ClipData using the tag as a label, the plain text // MIME type, and the already-created item. This creates a new // ClipDescription object within the ClipData and sets its MIME type // to "text/plain". val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. val myShadow = View.DragShadowBuilder(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. We use this imageView object // to create the default builder. View.DragShadowBuilder myShadow = new View.DragShadowBuilder(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; });
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. Di awal penarikan,
metode ini akan menampilkan ACTION_DRAG_STARTED
.
Untuk merespons 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 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 mengirim peristiwa tarik ke pemroses. Jika pemroses tidak dapat menerima peristiwa lepas, pemroses harus menampilkanfalse
, dan sistem akan berhenti mengirimkan peristiwa tarik ke pemroses hingga sistem mengirimkanACTION_DRAG_ENDED
untuk mengakhiri operasi tarik lalu lepas.
Untuk peristiwa ACTION_DRAG_STARTED
, metode DragEvent
berikut tidak
valid: getClipData()
,
getX()
,
getY()
, dan
getResult()
.
Menangani peristiwa selama operasi tarik
Selama tindakan tarik, pemroses peristiwa tarik yang menampilkan true
sebagai respons terhadap
peristiwa tarik ACTION_DRAG_STARTED
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 memutuskan apakah harus mengubah tampilan View
-nya.
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 tersebut.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 untuk menentukan posisi persis tempat pengguna dapat melepaskan konten. - Sebagai respons terhadap
ACTION_DRAG_EXITED
, pemroses harus mereset perubahan tampilan apa pun 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 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 tersebut. Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini tidak diperlukan.Menampilkan boolean
true
untuk menunjukkan bahwa operasi lepas berhasil diproses, ataufalse
jika belum. 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 operasi lepas untuk menampilkan posisi X dan Y dari
titik sentuh pada saat penurunan.
Meskipun pengguna dapat merilis bayangan tarik di atas View
yang pemroses peristiwa tariknya
tidak menerima peristiwa tarik, area kosong di UI aplikasi Anda, atau bahkan
di area di luar aplikasi Anda, Android tidak akan mengirim peristiwa dengan jenis
tindakan ACTION_DROP
dan hanya akan 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. Ini menunjukkan bahwa operasi tarik telah selesai.
Setiap pemroses peristiwa tarik harus melakukan hal berikut:
- Jika pemroses mengubah tampilannya selama operasi, pemroses harus mereset kembali ke tampilan default-nya sebagai indikasi visual kepada 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 saat sistem tidak mengirim peristiwaACTION_DROP
. - Untuk menunjukkan keberhasilan penyelesaian operasi lepas, pemroses
harus menampilkan boolean
true
ke sistem. Dengan tidak menampilkanfalse
, tanda visual yang menunjukkan drop shadow yang kembali ke sumbernya dapat menunjukkan kepada pengguna bahwa operasi tidak berhasil.
Merespons peristiwa tarik: Contoh
Semua peristiwa tarik diterima oleh metode atau pemroses peristiwa tarik. Cuplikan kode berikut adalah contoh 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; });
Menyesuaikan bayangan tarik
Anda dapat menentukan myDragShadowBuilder
yang disesuaikan dengan mengganti metode di
View.DragShadowBuilder
. Cuplikan kode berikut membuat bayangan tarik kecil
persegi panjang, abu-abu untuk TextView
:
Kotlin
private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) { private val shadow = ColorDrawable(Color.LTGRAY) // Define a callback that sends the drag shadow dimensions and touch point // back to the system. override fun onProvideShadowMetrics(size: Point, touch: Point) { // Set the width of the shadow to half the width of the original // View. val width: Int = view.width / 2 // Set the height of the shadow to half the height of the original // View. val height: Int = view.height / 2 // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height) // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height) // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2) } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas) } }
Java
private static class MyDragShadowBuilder extends View.DragShadowBuilder { // The drag shadow image, defined as a drawable object. private static Drawable shadow; // Constructor. public MyDragShadowBuilder(View view) { // Store the View parameter. super(view); // Create a draggable image that fills the Canvas provided by the // system. shadow = new ColorDrawable(Color.LTGRAY); } // Define a callback that sends the drag shadow dimensions and touch point // back to the system. @Override public void onProvideShadowMetrics (Point size, Point touch) { // Define local variables. int width, height; // Set the width of the shadow to half the width of the original // View. width = getView().getWidth() / 2; // Set the height of the shadow to half the height of the original // View. height = getView().getHeight() / 2; // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height); // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height); // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2); } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas); } }