O framework de arrastar e soltar do Android permite que você adicione ao seu app recursos interativos
que usam esse gesto. Com isso, os usuários podem copiar ou mover
textos, imagens, objetos e qualquer conteúdo que possa ser representado por um
URI de uma View
para
outra ou entre apps no modo de várias janelas.
![]() |
![]() |
|
|
O framework inclui uma classe de eventos de arrastar, listeners de arrastar e classes e métodos auxiliares. Embora tenha sido projetado principalmente a fim de permitir a transferência de dados, o framework pode ser usado para outras ações da IU. Por exemplo, você pode criar um app que mistura cores quando o usuário arrasta um ícone colorido sobre outro ícone. No entanto, o restante deste guia descreve o framework de arrastar e soltar no contexto da transferência de dados.
Visão geral
Uma operação de arrastar e soltar começa quando o usuário faz um gesto de IU que o
app reconhece como um sinal para começar a arrastar dados. Em resposta, o app
notifica o sistema sobre a inicialização de uma operação de arrastar e soltar. O sistema
chama o app de volta para receber uma representação dos dados que estão sendo arrastados, uma
ação de arrastar. Conforme o usuário move a ação de arrastar sobre o layout do app, o
sistema envia eventos de arrastar para os listeners de eventos de arrastar e os métodos de callback
associados aos objetos View
no layout. Se o usuário soltar a
ação de arrastar sobre uma visualização que pode aceitar os dados (um destino de soltar), o sistema
envia os dados para o destino. A operação de arrastar e soltar termina quando o usuário
solta a ação de arrastar, mesmo que ela não esteja sobre um destino de soltar.
Crie um listener de eventos de arrastar implementando um
View.OnDragListener
. Defina
o listener para um destino de soltar com o método
setOnDragListener()
do objeto View
. Cada visualização no layout também tem um
método de callback
onDragEvent()
.
O aplicativo notifica o sistema para iniciar uma operação de arrastar e soltar
chamando o método
startDragAndDrop()
,
que diz ao sistema para começar a enviar eventos de arrastar. O método também
fornece ao sistema os dados que o usuário está arrastando e os metadados que os
descrevem. Você pode chamar o método startDragAndDrop()
em qualquer View
no layout
atual. O sistema usa o objeto View
apenas para ter acesso às configurações globais
no layout.
Durante a operação de arrastar e soltar, o sistema envia eventos de arrastar para os listeners de eventos
de arrastar ou métodos de callback dos objetos View
no layout. Os
listeners ou métodos de callback usam os metadados para decidir se querem
aceitar os dados quando eles são soltos. Se o usuário soltar os dados em um destino de soltar
(uma View
que aceita os dados), o sistema vai enviar um objeto de evento de arrastar
contendo os dados ao listener de eventos de arrastar do destino de arrastar ou ao método
de callback.
Listeners de eventos de arrastar e métodos de callback
Uma View
recebe eventos de arrastar com um listener de eventos de arrastar que implementa um
View.OnDragListener
ou
com o método de callback
da visualização
onDragEvent()
. Quando o sistema chama o método ou listener, ele fornece um argumento
DragEvent
.
Na maioria dos casos, é preferível usar um listener que um método de callback. Ao
projetar IUs, você normalmente não cria subclasses de View
, mas usar o
método de callback força a criação de subclasses para substituir o método. Por
comparação, você pode implementar uma classe de listener e a usar com vários
objetos View
diferentes. Também é possível a implementar como uma classe in-line
anônima ou uma expressão lambda. Para definir o listener de um objeto View
, chame o método
setOnDragListener()
.
Como alternativa, a implementação padrão de onDragEvent()
pode ser
alterada sem substituir o método. Se você definir um
OnReceiveContentListener
em uma visualização (consulte
setOnReceiveContentListener()
), o método onDragEvent()
vai ter este comportamento padrão:
- Retorna verdadeiro em resposta à chamada para
startDragAndDrop()
Chama
performReceiveContent()
se os dados de arrastar e soltar forem soltos na visualizaçãoOs dados são transmitidos para o método como um objeto
ContentInfo
. O método invoca oOnReceiveContentListener
.Retorna verdadeiro se os dados de arrastar e soltar forem soltos na visualização e o
OnReceiveContentListener
consumir qualquer conteúdo
Defina o OnReceiveContentListener
para processar os dados específicos do
seu app. Se quiser oferecer compatibilidade com versões anteriores até a API nível 24, use a versão do
OnReceiveContentListener
do Jetpack.
Você pode ter um listener de eventos de arrastar e um método de callback para um objeto View
.
Nesse caso, o sistema chama o listener primeiro. O sistema não
chama o método de callback, a menos que o listener retorne false
.
A combinação dos métodos
onDragEvent()
e
View.OnDragListener
é
análoga à combinação de
onTouchEvent()
e
View.OnTouchListener
usados com eventos de toque.
Processo de arrastar e soltar
Existem basicamente quatro etapas ou estados no processo de arrastar e soltar: iniciado, em andamento, descartado e encerrado.
- Iniciado
Em resposta ao gesto de arrastar de um usuário, o app chama
startDragAndDrop()
para instruir o sistema a iniciar uma operação de arrastar e soltar. Os argumentos do método fornecem:- Os dados a serem arrastados
- Um callback para mostrar a ação de arrastar
- Metadados que descrevem os dados arrastados
Primeiro, o sistema responde chamando o aplicativo para extrair uma ação de arrastar. Em seguida, o sistema mostra a ação de arrastar no dispositivo.
Depois, o sistema envia um evento de arrastar com o tipo de ação
ACTION_DRAG_STARTED
para o listener de eventos de arrastar de todas asView
s no layout atual. Para continuar a receber eventos de arrastar, incluindo um possível evento de soltar, é necessário que um listener de eventos de arrastar retornetrue
. Isso registra o listener junto ao sistema. Somente listeners registrados continuam a receber eventos de arrastar. Neste ponto, os listeners também podem mudar a aparência do objetoView
do destino de soltar para mostrar que a visualização pode aceitar um evento de soltar.Se o listener de eventos de arrastar retornar
false
, ele não recebe eventos de arrastar para a operação atual até que o sistema envie um evento de arrastar com o tipo de açãoACTION_DRAG_ENDED
. Ao retornarfalse
, o listener informa ao sistema que ele não tem interesse na operação de arrastar e soltar e não quer aceitar os dados arrastados.- Em andamento
O usuário continua a ação de arrastar. Conforme a ação de arrastar cruza a caixa delimitadora de um destino de soltar, o sistema envia um ou mais eventos de arrastar ao listener de eventos de arrastar do destino. O listener pode optar por mudar a aparência do destino de soltar
View
em resposta ao evento. Por exemplo, se o evento indicar que a ação de arrastar entrou na caixa delimitadora do destino de soltar (tipo de açãoACTION_DRAG_ENTERED
), o listener pode reagir destacando aView
.- Solto
O usuário solta a ação de arrastar dentro da caixa delimitadora de um destino de soltar. O sistema envia ao listener do destino de soltar um evento de arrastar com o tipo de ação
ACTION_DROP
. O evento de arrastar contém os dados que foram transmitidos ao sistema na chamada para o métodostartDragAndDrop()
que iniciou a operação. Espera-se que o listener retorne o booleanotrue
ao sistema se o processamento dos dados descartados for concluído pelo listener.Essa etapa só ocorre se o usuário soltar a ação de arrastar dentro da caixa delimitadora de uma
View
cujo listener está registrado para receber eventos de arrastar (um destino de soltar). Se o usuário soltar a ação de arrastar em qualquer outra situação, nenhum evento de arrastarACTION_DROP
é enviado.- Encerrado
Depois que o usuário solta e ação de arrastar e após o sistema enviar um evento de arrastar com tipo de ação
ACTION_DROP
(se necessário), o sistema envia um evento de arrastar com o tipo de açãoACTION_DRAG_ENDED
para indicar que a operação de arrastar e soltar acabou. Isso é feito independente de onde o usuário tenha soltado a ação de arrastar. O evento é enviado a todos os listeners registrados para receber eventos de arrastar, mesmo se o listener também tiver recebido o eventoACTION_DROP
.
Cada uma dessas quatro etapas é descrita em mais detalhes em Uma operação de arrastar e soltar.
Eventos de arrastar
O sistema envia um evento de arrastar na forma de um objeto
DragEvent
, que contém um
tipo de ação que descreve o que acontece no processo de arrastar e soltar.
Dependendo do tipo de ação, o objeto também pode conter outros dados.
Os listeners de eventos de arrastar recebem o objeto DragEvent
. Para ver o tipo de ação,
os listeners chamam
DragEvent#getAction()
.
Há seis valores possíveis, definidos por constantes na classe DragEvent
.
Tabela 1. Tipos de ação de DragEvent.
Tipo de ação | Significado |
---|---|
ACTION_DRAG_STARTED |
O aplicativo chamou
startDragAndDrop() e recebeu uma ação de arrastar. Se o listener quiser continuar recebendo eventos de arrastar para a
operação, é necessário retornar o valor booleano true ao sistema.
|
ACTION_DRAG_ENTERED |
A ação de arrastar acabou de entrar na caixa delimitadora da View do listener de eventos de arrastar. Esse é o primeiro tipo de ação de evento que o
listener recebe quando a ação de arrastar entra na caixa delimitadora.
|
ACTION_DRAG_LOCATION |
Depois de um
evento ACTION_DRAG_ENTERED , a ação de arrastar ainda
está dentro da caixa delimitadora da View do listener de eventos de arrastar.
|
ACTION_DRAG_EXITED |
Após a ação
ACTION_DRAG_ENTERED e pelo menos um
evento ACTION_DRAG_LOCATION , a
ação de arrastar foi movida para fora da caixa delimitadora da View do listener de eventos de arrastar.
|
ACTION_DROP |
A ação de arrastar foi solta na View do listener de eventos de arrastar. Esse tipo de ação é enviado ao
listener de um objeto View somente se o listener tiver retornado o valor booleano true em resposta ao
evento de arrastar ACTION_DRAG_STARTED . Esse tipo de ação não é
enviado se o usuário solta a ação de arrastar em uma View cujo listener não está registrado
ou se ele solta a ação de arrastar em algo que não faz parte do layout atual.
Espera-se que o listener retorne valores booleanos |
ACTION_DRAG_ENDED |
O sistema está finalizando a operação de arrastar e soltar. Esse tipo de ação não é necessariamente
precedido por um evento ACTION_DROP . Se o sistema enviou
um evento ACTION_DROP , o recebimento do
tipo de ação ACTION_DRAG_ENDED não implica que a
operação de soltar foi concluída. O listener precisa chamar
getResult() (consulte a tabela 2)
para receber o valor retornado em resposta à ação
ACTION_DROP . Se um
evento ACTION_DROP não foi enviado,
getResult() retorna false .
|
O objeto DragEvent
também contém os dados e metadados que o
aplicativo forneceu ao sistema na chamada do método startDragAndDrop()
. Alguns
dados são válidos apenas para determinados tipos de ação, conforme resumido na
tabela 2. Para mais informações sobre eventos e os dados
associados, consulte Uma operação de arrastar e soltar.
Tabela 2. Dados válidos de DragEvent por tipo de ação
getAction() valor |
getClipDescription() valor |
getLocalState() valor |
getX() valor |
getY() valor |
getClipData() valor |
getResult() valor |
---|---|---|---|---|---|---|
ACTION_DRAG_STARTED |
✓ | ✓ | ✓ | ✓ | ||
ACTION_DRAG_ENTERED |
✓ | ✓ | ||||
ACTION_DRAG_LOCATION |
✓ | ✓ | ✓ | ✓ | ||
ACTION_DRAG_EXITED |
✓ | ✓ | ||||
ACTION_DROP |
✓ | ✓ | ✓ | ✓ | ✓ | |
ACTION_DRAG_ENDED |
✓ | ✓ |
Os métodos de DragEvent
getAction()
,
describeContents()
,
writeToParcel()
e
toString()
sempre retornam dados válidos.
Se um método não contiver dados válidos para um tipo de ação específico, ele retorna
null
ou 0, dependendo do tipo de resultado.
Ação de arrastar
Durante uma operação de arrastar e soltar, o sistema exibe uma imagem que o usuário arrasta. Para o movimento de dados, essa imagem representa os dados sendo arrastados. Para outras operações, a imagem representa algum aspecto da operação de arrastar.
A imagem é chamada de ação de arrastar. Você pode a criar usando os métodos que declara para um
objeto
View.DragShadowBuilder
. Você transmite o builder ao sistema quando inicia uma operação de arrastar e soltar
usando
startDragAndDrop()
.
Como parte da resposta a startDragAndDrop()
, o sistema invoca os
métodos de callback que você definiu em View.DragShadowBuilder
para ter uma ação
de arrastar.
A classe View.DragShadowBuilder
tem dois construtores:
View.DragShadowBuilder(View)
Esse construtor aceita todos os objetos
View
do aplicativo. O construtor armazena o objetoView
no objetoView.DragShadowBuilder
para que os callbacks possam o acessar a fim de construir a ação de arrastar. A visualização não precisa ser aView
(se houver) selecionada pelo usuário para iniciar a operação de arrastar.Se você usar esse construtor, não vai precisa estender o
View.DragShadowBuilder
nem substituir os métodos dele. Por padrão, você vê uma ação de arrastar com a mesma aparência daView
que é transmitida como argumento, centralizada no local em que o usuário está tocando na tela.View.DragShadowBuilder()
Se você usar esse construtor, nenhum objeto
View
estará disponível no objetoView.DragShadowBuilder
(o campo é definido comonull
). EstendaView.DragShadowBuilder
e substitua os métodos. Caso contrário, você vai ter uma ação de arrastar invisível. O sistema não gera um erro.
A classe View.DragShadowBuilder
tem dois métodos que, juntos, criam a
ação de arrastar:
onProvideShadowMetrics()
O sistema chama esse método imediatamente após
startDragAndDrop()
ser chamado. Use o método para enviar as dimensões e o ponto de contato da ação de arrastar ao sistema. O método tem dois parâmetros:- outShadowSize
- Um objeto
Point
. A largura da ação de arrastar fica na variávelx
e a altura emy
. - outShadowTouchPoint
- Um objeto
Point
. O ponto de contato é a área dentro da ação de arrastar que precisa estar sob o dedo do usuário durante a ação de arrastar. A posição X é armazenada emx
e a posição Y emy
.
onDrawShadow()
Imediatamente após a chamada do método
onProvideShadowMetrics()
, o sistema chamaonDrawShadow()
para criar a ação de arrastar. O método tem um único argumento, um objetoCanvas
que o sistema cria dos parâmetros fornecidos emonProvideShadowMetrics()
. O método exibe a ação de arrastar naCanvas
fornecida.
A fim de melhorar a performance, use um tamanho pequeno para a ação de arrastar. Para um único item, recomendamos usar um ícone. Para uma seleção múltipla, recomendamos usar ícones em uma pilha em vez de imagens completas espalhadas pela tela.
Uma operação de arrastar e soltar
Esta seção mostra de forma detalhada como iniciar uma ação de arrastar, responder a eventos durante a ação, responder a um evento de soltar e encerrar a operação de arrastar e soltar.
Iniciar uma ação de arrastar
O usuário inicia uma ação de arrastar com um gesto de arraste, normalmente tocar e manter pressionado, em um
objeto View
. Em resposta, o app precisa:
Criar um objeto
ClipData
e um objetoClipData.Item
para os dados que estão sendo movidos. Como parte doClipData
, forneça metadados armazenados em um objetoClipDescription
noClipData
. Para uma operação de arrastar e soltar que não representa a movimentação de dados, recomendamos usarnull
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 objetoClipData
que contém a tag (ou o identificador) de umaImageView
:Kotlin
// Create a string for the ImageView label. val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(this).apply { // Sets the bitmap for the ImageView from an icon bit map (defined elsewhere). setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG setOnLongClickListener { v -> // Create a new ClipData. // This is done in two steps to provide clarity. The convenience method // ClipData.newPlainText() can create a plain text ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. val item = ClipData.Item(v.tag as? CharSequence) // Create a new ClipData using the tag as a label, the plain text MIME type, and // the already-created item. This creates a new ClipDescription object within the // ClipData and sets its MIME type to "text/plain". val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiate the drag shadow builder. val myShadow = MyDragShadowBuilder(this) // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged myShadow, // The drag shadow builder null, // No need to use local data 0 // Flags (not currently used, set to 0) ) // Indicate that the long-click was handled. true } }
Java
// Create a string for the ImageView label. private static final String IMAGEVIEW_TAG = "icon bitmap"; ... // Create a new ImageView. ImageView imageView = new ImageView(this); // Set the bitmap for the ImageView from an icon bit map (defined elsewhere). imageView.setImageBitmap(iconBitmap); // Set the tag. imageView.setTag(IMAGEVIEW_TAG); // Sets a long click listener for the ImageView using an anonymous listener object that // implements the OnLongClickListener interface. imageView.setOnLongClickListener( v -> { // Create a new ClipData. // This is done in two steps to provide clarity. The convenience method // ClipData.newPlainText() can create a plain text ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. ClipData.Item item = new ClipData.Item((CharSequence) v.getTag()); // Create a new ClipData using the tag as a label, the plain text MIME type, and // the already-created item. This creates a new ClipDescription object within the // ClipData and sets its MIME type to "text/plain". ClipData dragData = new ClipData( (CharSequence) v.getTag(), new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item); // Instantiate the drag shadow builder. View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView); // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged myShadow, // The drag shadow builder null, // No need to use local data 0 // Flags (not currently used, set to 0) ); // Indicate that the long-click was handled. return true; });
O snippet de código abaixo define o
myDragShadowBuilder
substituindo os métodos emView.DragShadowBuilder
. O código cria uma ação de arrastar pequena, cinza e retangular para umaTextView
:Kotlin
private class MyDragShadowBuilder(v: View) : View.DragShadowBuilder(v) { private val shadow = ColorDrawable(Color.LTGRAY) // Defines a callback that sends the drag shadow dimensions and touch point // back to the system. override fun onProvideShadowMetrics(size: Point, touch: Point) { // Set the width of the shadow to half the width of the original View. val width: Int = view.width / 2 // Set the height of the shadow to half the height of the original View. val height: Int = view.height / 2 // The drag shadow is a ColorDrawable. This sets its dimensions to be the // same as the Canvas that the system provides. As a result, the drag shadow // fills the Canvas. shadow.setBounds(0, 0, width, height) // Set the size parameter's width and height values. These get back to // the system through the size parameter. size.set(width, height) // Set the touch point's position to be in the middle of the drag shadow. touch.set(width / 2, height / 2) } // Defines a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas) } }
Java
private static class MyDragShadowBuilder extends View.DragShadowBuilder { // The drag shadow image, defined as a drawable object. private static Drawable shadow; // Constructor public MyDragShadowBuilder(View v) { // Stores the View parameter. super(v); // Creates a draggable image that fills the Canvas provided by the system. shadow = new ColorDrawable(Color.LTGRAY); } // Defines a callback that sends the drag shadow dimensions and touch point // back to the system. @Override public void onProvideShadowMetrics (Point size, Point touch) { // Defines local variables int width, height; // Set the width of the shadow to half the width of the original View. width = getView().getWidth() / 2; // Set the height of the shadow to half the height of the original View. height = getView().getHeight() / 2; // The drag shadow is a ColorDrawable. This sets its dimensions to be the // same as the Canvas that the system provides. As a result, the drag shadow // fills the Canvas. shadow.setBounds(0, 0, width, height); // Set the size parameter's width and height values. These get back to the // system through the size parameter. size.set(width, height); // Set the touch point's position to be in the middle of the drag shadow. touch.set(width / 2, height / 2); } // Defines a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas); } }
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. A reação dos listeners é
chamar
DragEvent#getAction()
para extrair 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 listener de
eventos de arrastar precisa:
Chamar
DragEvent#getClipDescription()
e usar os métodos do tipo MIME noClipDescription
retornado para ver se o listener pode aceitar os dados arrastados.Se a operação de arrastar e soltar não representar movimento de dados, talvez isso não seja necessário.
Se o listener de eventos de arrastar puder aceitar uma ação de soltar, ele retorna
true
para informar ao sistema que continua a enviar eventos de arrastar ao listener. Se o listener não puder aceitar uma ação de soltar, ele retornafalse
e o sistema interrompe o envio de 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
, estes métodos de DragEvent
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 retornaram true
em resposta
ao evento de arrastar ACTION_DRAG_STARTED
continuarão recebendo eventos de arrastar. Os
tipos de eventos de arrastar que um listener recebe durante a ação de arrastar dependem do
local da ação e da visibilidade da View
do listener.
Os listeners usam os eventos de arrastar principalmente para decidir se mudarão a
aparência da View
deles.
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 ponto na tela sob o dedo ou o mouse do usuário) entra na caixa delimitadora daView
do listener.ACTION_DRAG_LOCATION
: depois que o listener receber um eventoACTION_DRAG_ENTERED
e antes de receber um eventoACTION_DRAG_EXITED
, ele recebe um novo eventoACTION_DRAG_LOCATION
sempre que o ponto de contato se mover. Os métodosgetX()
egetY()
retornam as coordenadas X e Y do ponto de contato.ACTION_DRAG_EXITED
: esse tipo de ação de evento é enviado para um listener que já recebeuACTION_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 é 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 degetX()
egetY()
, que correspondem ao local do ponto de contato. O listener pode usar essas informações para alterar a aparência daView
no ponto de contato ou determinar a posição exata em que o usuário pode soltar a ação de arrastar (ou seja, soltar os dados). - Em resposta à ação
ACTION_DRAG_EXITED
, é necessário que o listener redefina todas as mudanças de aparência aplicadas em resposta àACTION_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 solta a ação de arrastar sobre uma View
, e a View
já
informou que pode aceitar o conteúdo sendo arrastado, o sistema envia
um evento de arrastar à View
com o tipo de ação
ACTION_DROP
.
O listener de eventos de arrastar precisa:
Chamar
getClipData()
para receber o objetoClipData
que foi fornecido originalmente na chamada do métodostartDragAndDrop()
e processar os dados.Se a operação de arrastar e soltar não representar o movimento de dados, isso não é necessário.
Retornar o valor booleano
true
para indicar que a ação de soltar foi processada, oufalse
em caso de falha. O valor retornado se torna o valor retornado porgetResult()
para um possível eventoACTION_DRAG_ENDED
.Se o sistema não enviar um evento
ACTION_DROP
, o valor retornado porgetResult()
para um eventoACTION_DRAG_ENDED
éfalse
.
Para um evento ACTION_DROP
, getX()
e getY()
usam o sistema de coordenadas da
View
que recebeu a ação de soltar para retornar as posições X e Y do ponto
de contato no momento de soltar.
O sistema permite que o usuário solte a ação de arrastar sobre uma View
cujo listener
de eventos de arrastar não está recebendo esses eventos. Ele também permite que o usuário solte
a ação de arrastar sobre regiões vazias da IU do aplicativo ou sobre áreas
fora do app. Em todos esses casos, o sistema não envia
um evento com o tipo de ação ACTION_DROP
, embora o sistema envie um
evento ACTION_DRAG_ENDED
.
Responder ao fim de uma ação de arrastar
Imediatamente após o usuário soltar a ação de arrastar, o sistema envia um evento
de arrastar com um tipo de ação de
ACTION_DRAG_ENDED
a todos os listeners de eventos de arrastar no aplicativo. Isso indica que a
operação de arrastar e soltar acabou.
Cada listener de eventos de arrastar precisa fazer o seguinte:
- Se o listener tiver mudado a aparência do objeto
View
durante a operação, ele precisa redefinir aView
para a aparência padrão. Essa é uma indicação visual ao usuário de que a operação acabou. - O listener pode chamar o método
getResult()
para saber mais sobre a operação. Se um listener retornoutrue
em resposta a um evento de tipo de açãoACTION_DROP
, o métodogetResult()
retorna o booleanotrue
. Em todos os outros casos,getResult()
retorna o booleanofalse
, incluindo o caso em que o sistema não enviou um eventoACTION_DROP
. - Para indicar que a operação de arrastar e soltar teve sucesso, o
listener retorna o booleano
true
ao sistema.
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. O snippet de código abaixo é um exemplo simples de como responder a eventos de arrastar:
Kotlin
val imageView = ImageView(this) // Set the drag event listener for the View. imageView.setOnDragListener { v, e -> // Handles each of the expected events. when (e.action) { DragEvent.ACTION_DRAG_STARTED -> { // Determines if this View can accept the dragged data. if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example of what your application might do, applies a blue color tint // to the View to indicate that it can accept data. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Returns true to indicate that the View can accept the dragged data. true } else { // Returns false to indicate that, during the current drag and drop operation, // this View will not receive events again until ACTION_DRAG_ENDED is sent. false } } DragEvent.ACTION_DRAG_ENTERED -> { // Applies a green tint to the View. (v as? ImageView)?.setColorFilter(Color.GREEN) // Invalidates the view to force a redraw in the new tint. v.invalidate() // Returns true; the value is ignored. true } DragEvent.ACTION_DRAG_LOCATION -> // Ignore the event. true DragEvent.ACTION_DRAG_EXITED -> { // Resets the color tint to blue. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidates the view to force a redraw in the new tint. v.invalidate() // Returns true; the value is ignored. true } DragEvent.ACTION_DROP -> { // Gets the item containing the dragged data. val item: ClipData.Item = e.clipData.getItemAt(0) // Gets the text data from the item. val dragData = item.text // Displays a message containing the dragged data. Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show() // Turns off any color tints. (v as? ImageView)?.clearColorFilter() // Invalidates the view to force a redraw. v.invalidate() // Returns true. DragEvent.getResult() will return true. true } DragEvent.ACTION_DRAG_ENDED -> { // Turns off any color tinting. (v as? ImageView)?.clearColorFilter() // Invalidates the view to force a redraw. v.invalidate() // Does a getResult(), and displays what happened. when(e.result) { true -> Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG) else -> Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG) }.show() // Returns true; the value is ignored. true } else -> { // An unknown action type was received. Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") false } } }
Java
View imageView = new ImageView(this); // Set the drag event listener for the View. imageView.setOnDragListener( (v, e) -> { // Handles each of the expected events. switch(e.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Determines if this View can accept the dragged data. if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example of what your application might do, applies a blue color tint // to the View to indicate that it can accept data. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Returns true to indicate that the View can accept the dragged data. return true; } // Returns false to indicate that, during the current drag and drop operation, // this View will not receive events again until ACTION_DRAG_ENDED is sent. return false; case DragEvent.ACTION_DRAG_ENTERED: // Applies a green tint to the View. ((ImageView)v).setColorFilter(Color.GREEN); // Invalidates the view to force a redraw in the new tint. v.invalidate(); // Returns true; the value is ignored. return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event. return true; case DragEvent.ACTION_DRAG_EXITED: // Resets the color tint to blue. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidates the view to force a redraw in the new tint. v.invalidate(); // Returns true; the value is ignored. return true; case DragEvent.ACTION_DROP: // Gets the item containing the dragged data. ClipData.Item item = e.getClipData().getItemAt(0); // Gets the text data from the item. CharSequence dragData = item.getText(); // Displays a message containing the dragged data. Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show(); // Turns off any color tints. ((ImageView)v).clearColorFilter(); // Invalidates the view to force a redraw. v.invalidate(); // Returns true. DragEvent.getResult() will return true. return true; case DragEvent.ACTION_DRAG_ENDED: // Turns off any color tinting. ((ImageView)v).clearColorFilter(); // Invalidates the view to force a redraw. v.invalidate(); // Does a getResult(), and displays what happened. if (e.getResult()) { Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show(); } // Returns true; the value is ignored. return true; // An unknown action type was received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
Arrastar e soltar no modo de várias janelas
Os dispositivos com o Android 7.0 (nível 24 da API) ou mais recentes oferecem suporte ao modo de várias janelas. Isso permite que os usuários movam dados de um app para outro usando uma operação de arrastar e soltar. Consulte Suporte a várias janelas.
O app de origem fornece os dados. A operação de arrastar e soltar é iniciada no app de origem. O app de destino recebe os dados. A operação de arrastar e soltar termina no app de destino.
Ao iniciar a operação de arrastar e soltar, o app de origem precisa definir a sinalização
DRAG_FLAG_GLOBAL
para indicar que o usuário pode arrastar dados a outro app.
Como os dados atravessam os limites do app, eles compartilham o acesso aos dados usando um URI de conteúdo:
- O app de origem precisa definir uma ou ambas as sinalizações
DRAG_FLAG_GLOBAL_URI_READ
eDRAG_FLAG_GLOBAL_URI_WRITE
, dependendo do acesso de leitura/gravação aos dados que o app de origem quer conceder ao app de destino. - O app de destino precisa chamar o método
requestDragAndDropPermissions()
imediatamente antes de processar os dados que o usuário arrasta para o aplicativo. Se o app de destino não precisar mais de acesso aos dados da ação de arrastar e soltar, ele pode chamarrelease()
no objeto que foi retornado do métodorequestDragAndDropPermissions()
. Caso contrário, as permissões são liberadas quando a atividade que as contém for destruída.
O snippet de código abaixo mostra como liberar o acesso somente leitura para arrastar e soltar dados imediatamente após a operação de arrastar e soltar. Consulte o exemplo de DragAndDrop (link em inglês) no GitHub para ver uma demonstração mais completa.
Atividade de arrastar e soltar de origem
Kotlin
// Drag a file stored in internal storage. The file is in an "images/" directory. val internalImagesDir = File(context.filesDir, "images") val imageFile = File(internalImagesDir, imageFilename) val uri = FileProvider.getUriForFile(context, contentAuthority, imageFile) val listener = OnDragStartListener@{ view: View, _: DragStartHelper -> val clipData = ClipData(ClipDescription("Image Description", arrayOf("image/*")), ClipData.Item(uri)) // Must include DRAG_FLAG_GLOBAL to allow for dragging data between apps. // This example provides read-only access to the data. val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ return@OnDragStartListener view.startDragAndDrop(clipData, View.DragShadowBuilder(view), null, flags) } // Container where the image originally appears in the source app. val srcImageView = findViewById<ImageView>(R.id.imageView) // Detect and start the drag event. DragStartHelper(srcImageView, listener).apply { attach() }
Java
// Drag a file stored under an "images/" directory in internal storage. File internalImagesDir = new File(context.getFilesDir(), "images"); File imageFile = new File(internalImagesDir, imageFilename); final Uri uri = FileProvider.getUriForFile(context, contentAuthority, imageFile); // Container where the image originally appears in the source app. ImageView srcImageView = findViewById(R.id.imageView); // Enable the view to detect and start the drag event. new DragStartHelper(srcImageView, (view, helper) -> { ClipData clipData = new ClipData(new ClipDescription("Image Description", new String[] {"image/*"}), new ClipData.Item(uri)); // Must include DRAG_FLAG_GLOBAL to allow for dragging data between apps. // This example provides read-only access to the data. int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ; return view.startDragAndDrop(clipData, new View.DragShadowBuilder(view), null, flags); }).attach();
Atividade de arrastar e soltar de destino
Kotlin
// Container for where the image is to be dropped in the target app. val targetImageView = findViewById<ImageView>(R.id.imageView) targetImageView.setOnDragListener { view, event -> when (event.action) { ACTION_DROP -> { val imageItem: ClipData.Item = event.clipData.getItemAt(0) val uri = imageItem.uri // Request permission to access the image data being dragged into // the target activity's ImageView element. val dropPermissions = requestDragAndDropPermissions(event) (view as ImageView).setImageURI(uri) // Release the permission immediately afterwards because it's // no longer needed. dropPermissions.release() return@setOnDragListener true } // Implement logic for other DragEvent cases here. // An unknown action type was received. else -> { Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") return@setOnDragListener false } } }
Java
// Container where the image is to be dropped in the target app. ImageView targetImageView = findViewById(R.id.imageView); targetImageView.setOnDragListener( (view, event) -> { switch (event.getAction()) { case ACTION_DROP: ClipData.Item imageItem = event.getClipData().getItemAt(0); Uri uri = imageItem.getUri(); // Request permission to access the image data being // dragged into the target activity's ImageView element. DragAndDropPermissions dropPermissions = requestDragAndDropPermissions(event); ((ImageView)view).setImageURI(uri); // Release the permission immediately afterwards because // it's no longer needed. dropPermissions.release(); return true; // Implement logic for other DragEvent cases here. // An unknown action type was received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
DropHelper para operação de arrastar e soltar simplificada
A classe DropHelper
simplifica
a implementação de recursos de arrastar e soltar. Um membro da biblioteca
DragAndDrop
do Jetpack, o DropHelper
,
oferece compatibilidade com versões anteriores até o nível 24 da API.
Use o DropHelper
para especificar destinos de soltar, personalizar o destaque do destino de soltar
e definir como os dados descartados são processados.
Destinos de soltar
DropHelper#configureView()
é um método estático e sobrecarregado que permite especificar destinos de soltar. Os parâmetros incluem:
- A
Activity
atual (para permissões de URI) - Uma
View
que serve como destino de soltar - Os tipos MIME (link em inglês) dos dados soltos que o destino de soltar aceita
- Opções de configuração do destino de soltar, principalmente uma lista de campos EditText incorporados
- Um
OnReceiveContentListener
para processar dados soltos
Por exemplo, para criar um destino de soltar que aceite imagens, use uma das chamadas de método abaixo:
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);
A segunda chamada omite as opções de configuração de destino de soltar. Nesse caso, a cor de destaque do destino de soltar é definida como a cor secundária (ou de destaque) do tema, o raio do canto de destaque é definido como 16 dp e a lista de EditTexts fica vazia. Consulte Configuração de destino de soltar abaixo.
Configuração de destino de soltar
A classe interna DropHelper.Options
permite a configuração de destinos de soltar. Você fornece uma instância da
classe ao método
DropHelper.configureView(Activity, View, String[], Options,
OnReceiveContentListener)
.
Consulte Destinos de soltar acima.
Destaque de destino de soltar
O DropHelper
configura destinos de soltar para exibir um destaque enquanto os usuários arrastam
o conteúdo sobre os destinos. O DropHelper
oferece um estilo padrão, mas as
DropHelper.Options
permitem definir a cor do destaque e especificar
o raio do canto do retângulo do destaque.
Use a classe DropHelper.Options.Builder
para criar uma instância de DropHelper.Options
e definir opções de configuração,
por exemplo:
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();
Componentes de EditText
em destinos de soltar
O DropHelper
também controla o foco no destino de soltar quando o destino
contém campos de texto editáveis.
Os destinos de soltar podem ser uma visualização única ou uma hierarquia de visualização. Se a hierarquia de visualização do destino
de soltar contiver um ou mais componentes EditText
,
forneça uma lista dos componentes a
DropHelper.Options.Builder#addInnerEditTexts(EditText...)
para garantir que o destaque do destino de soltar e o processamento de dados de texto funcionem corretamente.
O DropHelper
impede que componentes EditText
na hierarquia de visualização
do destino de soltar roubem o foco da visualização contida durante interações
de arrastar.
Além disso, se o ClipData
de arrastar e soltar incluir dados de texto e URI, o DropHelper
seleciona um dos componentes EditText
no destino de soltar para processar dados de texto. A seleção é baseada nesta
ordem de precedência:
- O
EditText
em que oClipData
foi solto - O
EditText
que contém o cursor de texto (circunflexo) - O primeiro
EditText
fornecido à chamada doDropHelper.Options.Builder#addInnerEditTexts(EditText...)
Para definir um EditText
como o gerenciador de dados de texto padrão, transmita o EditText
como
o primeiro argumento da chamada para o
DropHelper.Options.Builder#addInnerEditTexts(EditText...)
. Por exemplo, se
o destino de soltar processar imagens, mas tiver campos de texto editáveis T1
, T2
e T3
, torne o T2
o padrão desta maneira:
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();
Processamento de dados de destino de soltar
O método DropHelper#configureView()
aceita um OnReceiveContentListener
que você cria para processar o ClipData
de arrastar e soltar. Os dados de arrastar e soltar
são fornecidos ao listener em um objeto
ContentInfoCompat
.
Há dados de texto no objeto. Objetos de mídia, como imagens, são representados por
URIs.
O OnReceiveContentListener
também processa os dados fornecidos ao destino de soltar por
interações do usuário que não sejam de arrastar e soltar (como copiar e colar) quando o método
DropHelper#configureView()
é usado para configurar os tipos de visualizações abaixo:
- Todas as visualizações, se o usuário estiver usando o Android 12 ou mais recente
AppCompatEditText
até o Android 7.0
Tipos MIME, permissões e validação de conteúdo
A verificação de tipo MIME do DropHelper
é baseada na ação de arrastar e soltar
ClipDescription
, que é
criada pelo app que fornece os dados de arrastar e soltar. Valide a
ClipDescription
para garantir que os tipos MIME tenham sido definidos corretamente.
O DropHelper
solicita todas as permissões de acesso aos URIs de conteúdo contidos no
ClipData
de arrastar e soltar (consulte
DragAndDropPermissions
).
As permissões resolvem os URIs de conteúdo ao processar os
dados de arrastar e soltar.
O DropHelper
não valida os dados retornados pelos provedores de conteúdo ao
resolver URIs nos dados soltos. Confira se há valores nulos e verifique se os
dados resolvidos estão corretos.