Você pode implementar o processo de arrastar e soltar em visualizações respondendo a eventos que podem acionar o início de uma ação de arrastar e responder e consumir eventos de soltar.
Iniciar uma ação de arrastar
O usuário inicia uma ação de arrastar com um gesto, geralmente tocando ou clicando e segurando um item que desejam arrastar.
Para processar isso em um View
, crie um
objeto ClipData
e
Objeto ClipData.Item
para
os dados que estão sendo movidos. Como parte do ClipData
, forneça metadados que sejam
armazenadas em um
Objeto ClipDescription
no ClipData
. Para uma operação de arrastar e soltar que não representa
movimento de dados, use null
em vez de um objeto real.
Por exemplo, o snippet de código abaixo mostra como responder a um gesto de
tocar e manter pressionado em uma ImageView
, criando um objeto ClipData
que contém a
tag (ou o identificador) de uma 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; });
Responder ao início de uma ação de arrastar
Durante a operação de arrastar, o sistema envia eventos de arrastar para os listeners de
eventos de arrastar dos objetos View
do layout atual. Os ouvintes reagem
chame DragEvent.getAction()
para receber o tipo de ação. No início de uma ação de arrastar,
Esse método retorna ACTION_DRAG_STARTED
.
Em resposta a um evento com o tipo de ação ACTION_DRAG_STARTED
, um evento de arrastar
listener precisa fazer o seguinte:
Ligação
DragEvent.getClipDescription()
e usar os métodos do tipo MIME noClipDescription
retornado para ver se o listener pode aceitar os dados que estão sendo arrastados.Se a operação de arrastar e soltar não representar movimento de dados, isso pode desnecessária.
Se o listener de eventos de arrastar puder aceitar uma ação de soltar, ele precisará retornar
true
para informar o sistema continue a enviar eventos de arrastar ao listener. Se o listener não aceita uma ação de soltar, o listener precisará retornarfalse
, e o sistema será interrompido enviar eventos de arrastar ao listener até que o sistema envieACTION_DRAG_ENDED
para concluir a operação de arrastar e soltar.
Para um evento ACTION_DRAG_STARTED
, os métodos DragEvent
a seguir não são
válidos: getClipData()
,
getX()
,
getY()
e
getResult()
.
Gerenciar eventos durante a ação de arrastar
Durante a ação de arrastar, os listeners de eventos de arrastar que retornam true
em resposta à
o evento de arrastar ACTION_DRAG_STARTED
continuar recebendo eventos de arrastar. Os tipos
dos eventos de arrastar que um listener recebe durante a ação de arrastar dependem do local do
a sombra de arrastar e a visibilidade da View
do listener. Os listeners usam o recurso de arrastar
eventos principalmente para decidir se eles precisam mudar a aparência do View
.
Durante a ação de arrastar, DragEvent.getAction()
retorna um dos três valores:
ACTION_DRAG_ENTERED
: o listener recebe esse tipo de ação de evento quando o ponto de contato (o na tela sob o dedo ou o mouse do usuário, entra no caixa delimitadora doView
do listener.ACTION_DRAG_LOCATION
: quando o listener recebe um eventoACTION_DRAG_ENTERED
, ele recebe um novoACTION_DRAG_LOCATION
sempre que o ponto de contato se mover até recebe um eventoACTION_DRAG_EXITED
. Os métodosgetX()
egetY()
retorna as coordenadas X e Y do ponto de contato.ACTION_DRAG_EXITED
: esse tipo de ação de evento é enviado ao listener que recebeACTION_DRAG_ENTERED
. O evento é enviado quando o ponto de contato da ação de arrastar é movido de dentro da caixa delimitadora daView
do listener para fora dessa caixa.
O listener de eventos de arrastar não precisa reagir a nenhum desses tipos de ação. Se o listener retornar um valor ao sistema, ele será ignorado.
Veja abaixo algumas diretrizes para responder a cada um desses tipos de ação:
- Em resposta a
ACTION_DRAG_ENTERED
ouACTION_DRAG_LOCATION
, o listener pode mudar a aparência daView
para indicar que a visualização é um possível destino de soltar. - Um evento com o tipo de ação
ACTION_DRAG_LOCATION
contém dados válidos paragetX()
egetY()
correspondentes à localização do ponto de contato. A listener pode usar essas informações para mudar a aparência doView
no ponto de contato ou para determinar a posição exata em que o usuário pode soltar o conteúdo. - Em resposta a
ACTION_DRAG_EXITED
, o listener precisa redefinir qualquer aparência mudanças que ele aplica em resposta aACTION_DRAG_ENTERED
ouACTION_DRAG_LOCATION
Isso indica ao usuário que aView
deixou de ser um destino para uma ação de soltar iminente.
Responder a uma ação de soltar
Quando o usuário soltar a ação de arrastar sobre uma View
e a View
anteriormente
informar que pode aceitar o conteúdo sendo arrastado, o sistema enviará um
arraste o evento para View
com o tipo de ação ACTION_DROP
.
O listener de eventos de arrastar precisa fazer o seguinte:
Chame
getClipData()
para receber o objetoClipData
que é originalmente fornecido na chamada parastartDragAndDrop()
e processar os dados. Se a operação de arrastar e soltar não representar dados movimento, isso é desnecessário.Retorne o booleano
true
para indicar que a ação de soltar foi processada. oufalse
se não for. O valor retornado se torna o valor retornado porgetResult()
para um possível eventoACTION_DRAG_ENDED
. Se o sistema não enviar um eventoACTION_DROP
, o valor retornado porgetResult()
para um eventoACTION_DRAG_ENDED
éfalse
.
Para um evento ACTION_DROP
, getX()
e getY()
usam o sistema de coordenadas de
o View
que recebe a ação de soltar para retornar as posições X e Y do
um ponto de contato no momento da ação de soltar.
O usuário pode soltar a sombra de arraste sobre uma View
com um evento de arrastar
não está recebendo eventos de arrastar, regiões vazias da interface do app ou mesmo
em áreas fora do aplicativo, o Android não enviará um evento com ação
digite ACTION_DROP
e enviará apenas um evento ACTION_DRAG_ENDED
.
Responder ao fim de uma ação de arrastar
Imediatamente após o usuário soltar a sombra da ação de arrastar, o sistema envia uma ação de arrastar
evento com um tipo de ação ACTION_DRAG_ENDED
para todos os listeners de eventos de arrastar
no seu aplicativo. Isso indica que a operação de arrastar foi concluída.
Cada listener de eventos de arrastar precisa fazer o seguinte:
- Se o listener alterar sua aparência durante a operação, ele deverá ser redefinido de volta à aparência padrão como indicação visual para o usuário de que o operação for concluída.
- O listener pode chamar o método
getResult()
para saber mais sobre a operação. Se um listener retornartrue
em resposta a um evento de ação digiteACTION_DROP
, egetResult()
retorna o booleanotrue
. Em todos os outros casos,getResult()
retorna o booleanofalse
, inclusive quando o sistema não envia um eventoACTION_DROP
. - Para indicar a conclusão da operação de soltar, o listener
retornará o booleano
true
ao sistema. Ao não retornarfalse
, uma um sinal visual que mostra a sombra projetada retornando à origem pode sugerir ao usuário de que a operação não foi bem-sucedida.
Responder a eventos de arrastar: um exemplo
Todos os eventos de arrastar são acessados pelo método de evento de arrastar ou pelo listener. A O snippet de código a seguir é um exemplo de resposta a eventos de arrastar:
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; });
Personalizar uma sombra de arraste
Você pode definir um myDragShadowBuilder
personalizado substituindo os métodos
View.DragShadowBuilder
. O snippet de código a seguir cria uma classe
sombra de arraste retangular e cinza para uma 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); } }