Com o framework de arrastar e soltar do Android, você pode permitir que os usuários movam dados de uma visualização para outra usando um gesto gráfico de arrastar e soltar. O framework inclui uma classe de evento e listeners de arrastar e métodos e classes auxiliares.
Embora o framework seja projetado principalmente para a movimentação de dados, ele 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 tópico descreve o framework em termos de movimentação de dados.
Consulte também os seguintes recursos relacionados:
Geral
Uma operação de arrastar e soltar é iniciada quando o usuário faz algum gesto reconhecido como
sinal para começar a arrastar dados. Em resposta, seu aplicativo informa ao sistema que a ação de arrastar está
sendo iniciada. O sistema chama o aplicativo novamente para receber uma representação dos dados que estão
sendo arrastados. Conforme o dedo do usuário move essa representação (uma “sombra da ação de arrastar”)
sobre o layout atual, o sistema envia eventos de arrastar para os objetos de listener de eventos de arrastar e
os métodos de callback de evento de arrastar associados aos objetos View
no layout.
Quando o usuário solta a sombra da ação de arrastar, o sistema encerra a operação de arrastar.
Crie um objeto de listener de eventos de arrastar ("listeners") a partir de uma classe que implemente
View.OnDragListener
. Defina o objeto de listener de eventos de arrastar para uma visualização
com o método
setOnDragListener()
do objeto dela.
Os objetos de visualização também têm um método de callback onDragEvent()
. Ambos são descritos mais detalhadamente na seção
Listener de eventos de arrastar e método de callback.
Observação: para simplificar, as seções a seguir se referem à rotina que recebe eventos de arrastar como o “listener de eventos de arrastar”, mesmo que ele possa ser na verdade um método de callback.
Ao iniciar uma ação de arrastar, inclua os dados que você está movendo e os metadados que descrevem esses dados como parte da chamada para o sistema. Durante a ação de arrastar, o sistema envia eventos de arrastar para os listeners de eventos de arrastar ou métodos de callback de cada visualização no layout. Os listeners ou os métodos de callback podem usar os metadados para decidir se querem aceitar os dados quando eles são soltos. Se o usuário soltar os dados sobre um objeto de visualização e o listener ou método de callback dessa visualização informou previamente ao sistema que quer aceitar a ação de soltar, o sistema enviará os dados ao listener ou método de callback em um evento de arrastar.
O aplicativo avisa o sistema para iniciar uma ação de arrastar chamando
o método
startDrag()
. Isso diz ao sistema para começar a enviar eventos de arrastar. O método também envia os dados que
você está arrastando.
Você pode chamar
startDrag()
para qualquer visualização anexada no layout atual. O sistema só usa o objeto de visualização para ter acesso
às configurações globais no seu layout.
Quando o aplicativo chamar
startDrag()
,
o restante do processo usará eventos que o sistema envia para os objetos de visualização no layout
atual.
Observação: se os apps estiverem sendo executados no modo de várias janelas, os usuários poderão arrastar e soltar dados de um app para o outro. Para mais informações, consulte Suporte a arrastar e soltar.
O processo de arrastar e soltar
Existem basicamente quatro etapas ou estados no processo de arrastar e soltar:
- Iniciado
-
Em resposta ao gesto do usuário para iniciar uma ação de arrastar, o aplicativo chama
startDrag()
para dizer ao sistema para começar a arrastar. Os argumentosstartDrag()
fornecem os dados a serem arrastados, os metadados para esses dados e um callback para desenhar a sombra da ação de arrastar.Primeiro, o sistema responde chamando seu aplicativo para ter uma sombra da ação de arrastar. Em seguida, ele exibe a sombra da ação de arrastar no dispositivo.
Depois disso, o sistema envia um evento de arrastar com o tipo de ação
ACTION_DRAG_STARTED
aos listeners de eventos de arrastar para todos os objetos de visualização 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 objeto de visualização para mostrar que o listener pode aceitar um evento de soltar.Se o listener de evento de arrastar retornar
false
, ele não receberá 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 enviarfalse
, o listener informa ao sistema que ele não tem interesse na operação de arrastar e não aceitará os dados arrastados. - Em andamento
-
O usuário continua a arrastar. Conforme a sombra da ação de arrastar cruza a caixa delimitadora de um objeto
de visualização, o sistema envia um ou mais eventos de arrastar ao listener de
eventos do objeto, se estiver registrado para receber eventos. O listener pode escolher
mudar a aparência do objeto de visualização em resposta ao evento. Por exemplo, se o evento
indicar que a sombra da ação de arrastar entrou na caixa delimitadora da visualização
(tipo de ação
ACTION_DRAG_ENTERED
), o listener poderá reagir destacando a visualização. - Solto
-
O usuário libera a sombra da ação de arrastar dentro da caixa delimitadora de uma visualização que pode aceitar os
dados. O sistema envia ao listener do objeto de visualização 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 ostartDrag()
que iniciou a operação. Espera-se que o listener retorne o valor booleanotrue
para o sistema se o código para aceitar a ação de soltar for bem-sucedido.Observe que essa etapa só ocorrerá se o usuário soltar a sombra da ação de arrastar dentro da caixa delimitadora de uma visualização cujo listener está registrado para receber eventos de arrastar. Se o usuário soltar a sombra da ação de arrastar em qualquer outra situação, nenhum evento de arrastar
ACTION_DROP
será enviado. - Encerrado
-
Depois que o usuário libera a sombra da 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 acabou. Isso é feito independentemente de onde o usuário tenha liberado a sombra da ação de arrastar. O evento é enviado para todos os listeners registrados para receber eventos de arrastar, mesmo se o listener tiver recebido o eventoACTION_DROP
.
Cada uma dessas quatro etapas é descrita em mais detalhes na seção Projetar uma operação de arrastar e soltar.
Listener de eventos de arrastar e método de callback
Uma visualização recebe eventos de arrastar com um listener de eventos de arrastar que implementa
View.OnDragListener
ou com o
método de callback onDragEvent(DragEvent)
.
Quando o sistema chama o método ou listener, um objeto DragEvent
é transmitido a ele.
É provável que você queira usar o listener na maioria dos casos. Ao projetar IUs, você normalmente
não cria subclasses de visualização, mas usar o método de callback força você a fazer isso para
modificar o método. Por outro lado, você pode implementar uma classe de listener e usá-la com
vários objetos de visualização diferentes. Também é possível implementá-la como uma classe in-line anônima. Para
definir o listener para um objeto de visualização, chame setOnDragListener()
.
Você pode ter um listener e um método de callback para o objeto de visualização. Se isso ocorrer,
o sistema chamará 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(DragEvent)
e
View.OnDragListener
é análoga à combinação
de onTouchEvent()
e
View.OnTouchListener
usada com eventos de toque.
Eventos de arrastar
O sistema envia um evento de arrastar na forma de um objeto DragEvent
. O
objeto contém um tipo de ação que informa ao listener o que está acontecendo no processo
de arrastar e soltar. O objeto contém outros dados, dependendo do tipo de ação.
Para receber o tipo de ação, o listener chama getAction()
. Há
seis valores possíveis, definidos por constantes na classe DragEvent
. Eles
estão listados na Tabela 1.
O objeto DragEvent
também contém os dados fornecidos pelo seu aplicativo
ao sistema na chamada para
startDrag()
.
Alguns dados são válidos apenas para determinados tipos de ação. Na Tabela 2, veja um resumo dos dados
válidos para cada tipo de ação. Eles também são descritos em detalhes com
o evento para o qual são válidos na seção
Projetar uma operação de arrastar e soltar.
Tabela 1. Tipos de ação de DragEvent.
Valor getAction() | Significado |
---|---|
ACTION_DRAG_STARTED |
O listener de eventos de arrastar de um objeto de visualização recebe esse tipo de ação de evento logo após
o aplicativo chamar
startDrag() e
receber uma sombra da ação de arrastar.
|
ACTION_DRAG_ENTERED |
O listener de eventos de arrastar de um objeto de visualização recebe esse tipo de ação de evento logo após a sombra da ação de arrastar
entrar na caixa delimitadora da visualização. Esse é o primeiro tipo de ação de evento que o
listener recebe quando a sombra da ação de arrastar entra na caixa delimitadora. Se o listener quiser
continuar recebendo eventos de arrastar para a operação, será necessário retornar o valor booleano
true para o sistema.
|
ACTION_DRAG_LOCATION |
O listener de eventos de arrastar de um objeto de visualização recebe esse tipo de ação de evento depois de receber um
evento ACTION_DRAG_ENTERED enquanto a sombra da ação de arrastar ainda
está dentro da caixa delimitadora da visualização.
|
ACTION_DRAG_EXITED |
O listener de eventos de arrastar de um objeto de visualização recebe esse tipo de ação após receber um evento
ACTION_DRAG_ENTERED e pelo menos um
evento ACTION_DRAG_LOCATION e depois de o usuário ter movido
a sombra da ação de arrastar para fora da caixa delimitadora.
|
ACTION_DROP |
O listener de eventos de arrastar de um objeto de visualização recebe esse tipo de ação de evento quando o usuário
libera a sombra da ação de arrastar sobre o objeto de visualização. Esse tipo de ação é enviado ao listener de um objeto de
visualização apenas 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 libera a sombra da ação de arrastar em uma visualização cujo listener não está registrado
ou se o usuário solta a sombra da 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 listener de eventos de arrastar de um objeto de visualização recebe esse tipo de ação de evento
quando o sistema está finalizando a operação de arrastar. Esse tipo de ação não é necessariamente
precedido por um evento ACTION_DROP . Se o sistema enviou
um 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() para receber o valor
retornado em resposta a ACTION_DROP . Se um
evento ACTION_DROP não foi enviado,
getResult() retorna false .
|
Tabela 2. Dados válidos de DragEvent por tipo de ação
Valor getAction() |
Valor getClipDescription() |
Valor getLocalState() |
Valor getX() |
Valor getY() |
Valor getClipData() |
Valor 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 |
Os métodos 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 retornará
null
ou 0, dependendo do tipo de resultado.
A sombra da 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 sombra da ação de arrastar. Você pode criá-la com métodos que você declara para um
objeto View.DragShadowBuilder
e, em seguida, transmiti-la ao sistema quando
começar a arrastar usando
startDrag()
.
Como parte da resposta a
startDrag()
,
o sistema invoca os métodos de callback que você definiu em
View.DragShadowBuilder
para ter uma sombra da ação de arrastar.
A classe View.DragShadowBuilder
tem dois construtores:
View.DragShadowBuilder(View)
-
Esse construtor aceita todos os objetos
View
do seu aplicativo. O construtor armazena o objeto de visualização no objetoView.DragShadowBuilder
para que você possa acessá-lo durante o callback enquanto constrói a sombra da ação de arrastar. Ele não precisa estar associado à visualização (se houver) selecionada pelo usuário para iniciar a operação de arrastar.Se você usar esse construtor, não precisará estender
View.DragShadowBuilder
nem modificar os métodos dele. Por padrão, você receberá uma sombra da ação de arrastar com a mesma aparência da visualização 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 de visualização estará disponível no
objeto
View.DragShadowBuilder
, o campo estará definido comonull
. Se você usar esse construtor e não estenderView.DragShadowBuilder
nem modificar os métodos dele, terá uma sombra da ação de arrastar invisível. O sistema não apresentará um erro.
A classe View.DragShadowBuilder
tem dois métodos:
-
onProvideShadowMetrics()
-
O sistema chama esse método imediatamente após
startDrag()
ser chamado. Use-o para enviar ao sistema as dimensões e o ponto de contato da sombra da ação de arrastar. O método tem dois argumentos:- dimensions
-
Um objeto
Point
. A largura da sombra da ação de arrastar aparece emx
, e a altura aparece emy
. - touch_point
-
Um objeto
Point
. O ponto de toque é a área dentro da sombra da ação de arrastar que precisa estar sob o dedo do usuário durante a ação de arrastar. A posição X aparece emx
, e a posição Y aparece emy
-
onDrawShadow()
-
Imediatamente após a chamada para
onProvideShadowMetrics()
, o sistema chamaonDrawShadow()
para ter a sombra da ação de arrastar. O método tem um único argumento, um objetoCanvas
que o sistema constrói a partir dos parâmetros fornecidos emonProvideShadowMetrics()
. Use-o para desenhar a sombra da ação de arrastar no objetoCanvas
fornecido.
Para melhorar o desempenho, mantenha o tamanho da sombra da ação de arrastar reduzido. 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.
Projetar uma operação de arrastar e soltar
Esta seção mostra passo a passo 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 arrastar, normalmente tocando em um objeto de visualização e o mantendo pressionado. Em resposta, faça o seguinte:
-
Se necessário, crie um
ClipData
e umClipData.Item
para os dados que estão sendo movidos. Como parte do objeto ClipData, forneça os metadados armazenados em um objetoClipDescription
no ClipData. Para uma operação de arrastar e soltar que não represente movimentação de dados, recomendamos usarnull
em vez de um objeto real.Por exemplo, o snippet de código a seguir mostra como responder à ação de tocar em uma ImageView e mantê-la pressionada, criando um objeto ClipData que contenha a tag ou o rótulo de uma ImageView. Após este snippet, o próximo mostra como modificar os métodos em
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) ); } }
-
O snippet de código a seguir define
myDragShadowBuilder
.in Ele cria uma sombra da ação de arrastar para arrastar uma TextView como um pequeno retângulo cinza: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); } }
Observação: lembre-se de que não é necessário estender
View.DragShadowBuilder
. O construtorView.DragShadowBuilder(View)
cria uma sombra da ação de arrastar padrão que tem o mesmo tamanho que o argumento de visualização transmitido para ela, com o ponto de toque centralizado na sombra da ação de arrastar.
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 de visualização do layout atual. A reação dos listeners será
chamar 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 listener fará o seguinte:
-
Chamar
getClipDescription()
para ter oClipDescription
. Use os métodos do tipo MIME emClipDescription
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, talvez isso não seja necessário.
-
Se o listener puder aceitar uma ação de soltar, ele retornará
true
. Isso diz ao sistema para continuar a enviar eventos de arrastar para o listener. Se ele não puder aceitar uma ação de soltar, retornaráfalse
, e o sistema deixará de enviar eventos de arrastar até que ele envieACTION_DRAG_ENDED
.
Observe que para um evento ACTION_DRAG_STARTED
,
os seguintes métodos 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 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
sombra da ação de arrastar e da visibilidade da visualização do listener.
Durante a ação de arrastar, os listeners usam principalmente eventos de arrastar para decidir se mudarão a aparência da visualização.
Durante a ação de arrastar, getAction()
retorna um dos três
valores:
-
ACTION_DRAG_ENTERED
: o listener recebe isso quando o ponto de toque (o ponto na tela sob o dedo do usuário) entra na caixa delimitadora da visualização do listener. -
ACTION_DRAG_LOCATION
: depois que o listener receber um eventoACTION_DRAG_ENTERED
e antes de receber um eventoACTION_DRAG_EXITED
, ele receberá um novo eventoACTION_DRAG_LOCATION
sempre que o ponto de toque se mover. Os métodosgetX()
egetY()
retornam as coordenadas X e Y do ponto de toque. -
ACTION_DRAG_EXITED
: esse evento é enviado a um listener que já havia recebidoACTION_DRAG_ENTERED
depois que a sombra da ação de arrastar não está mais dentro da caixa delimitadora da visualização do listener.
O listener não precisa reagir a nenhum desses tipos de ação. Se o listener retornar um valor para o sistema, ele será ignorado. Veja 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 da visualização para indicar que algo está prestes a ser solto sobre ela. -
Um evento com o tipo de ação
ACTION_DRAG_LOCATION
contém dados válidos paragetX()
egetY()
, que correspondem ao local do ponto de toque. O listener pode querer usar essas informações para mudar a aparência da parte da visualização que está no ponto de toque. O listener também pode usar essas informações para determinar a posição exata em que o usuário soltará a sombra da ação de arrastar. -
Em resposta a
ACTION_DRAG_EXITED
, é necessário que o listener redefina todas as mudanças de aparência aplicadas em resposta aACTION_DRAG_ENTERED
ouACTION_DRAG_LOCATION
. Isso indica ao usuário que a visualização deixou de ser um destino para uma ação de soltar iminente.
Responder a uma ação de soltar
Quando o usuário liberar a sombra da ação de arrastar em uma visualização no aplicativo, e a visualização tiver
informado previamente que poderia aceitar o conteúdo sendo arrastado, o sistema enviará um evento de arrastar
para essa visualização com o tipo de ação ACTION_DROP
. O listener
fará o seguinte:
-
Chamará
getClipData()
para receberClipData
que foi fornecido originalmente na chamada parastartDrag()
e o armazenará. Se a operação de arrastar e soltar não representar movimento de dados, talvez isso não seja necessário. -
Retornará o valor booleano
true
para indicar que a ação de soltar foi processada com êxito, ou o booleanofalse
se não foi. O valor retornado se torna o valor retornado porgetResult()
para um eventoACTION_DRAG_ENDED
.Observe que se o sistema não enviar um evento
ACTION_DROP
, o valor degetResult()
para um eventoACTION_DRAG_ENDED
seráfalse
.
Para um evento ACTION_DROP
,
getX()
e getY()
retornam as posições X e Y do ponto da ação de arrastar no momento da ação de soltar, usando o sistema
de coordenadas da visualização que recebeu a ação de soltar.
O sistema permite que o usuário solte a sombra da ação de arrastar em uma visualização cujos listeners não estão
recebendo eventos de arrastar. Ele também permite que o usuário solte a sombra da ação
de arrastar nas regiões vazias da IU do aplicativo ou em áreas fora do seu aplicativo.
Em todos esses casos, o sistema não envia um evento com o tipo de ação
ACTION_DROP
, embora um evento
ACTION_DRAG_ENDED
seja enviado.
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
um evento de arrastar para todos os listeners de eventos de arrastar no seu aplicativo, com um tipo de ação de
ACTION_DRAG_ENDED
. Isso indica que a operação de arrastar
acabou.
Cada listener deve fazer o seguinte:
- Se o listener tiver mudado a aparência do objeto de visualização durante a operação, é necessário redefinir a visualização para a aparência padrão. Essa é uma indicação visual para o usuário de que a operação acabou.
-
O listener pode chamar
getResult()
para saber mais sobre a operação. Se um listener retornoutrue
em resposta a um evento de tipo de açãoACTION_DROP
, entãogetResult()
retornará um booleanotrue
. Em todos os outros casos,getResult()
retorna o booleanofalse
, incluindo qualquer caso em que o sistema não tenha enviado um eventoACTION_DROP
. -
O listener retornará o booleano
true
ao sistema.
Responder a eventos de arrastar: um exemplo
Todos os eventos de arrastar são recebidos inicialmente pelo método de evento de arrastar ou pelo listener. O snippet de código a seguir é um exemplo simples de reação aos eventos de arrastar em um listener:
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; } };
Permissões de arrastar no modo de várias janelas
Os dispositivos com o Android 7.0 (API de nível 24) ou versões posteriores são compatíveis com o modo de várias janelas, permitindo que os usuários movam dados de um app para o outro usando uma operação de arrastar e soltar:
- App de origem: o app que originalmente contém os dados. É onde a ação de arrastar se inicia.
- App de destino: o app que recebe dados. É onde a ação de arrastar termina.
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 para 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 destino terá. - O app de destino precisa chamar
requestDragAndDropPermissions()
imediatamente antes de gerenciar os dados que o usuário arrasta para o aplicativo. Se o aplicativo de destino não precisar mais de acesso aos dados da ação de arrastar, o app poderá chamarrelease()
no objeto que foi retornado derequestDragAndDropPermissions()
. Caso contrário, a permissão será liberada quando a atividade que a contém for destruída.
O snippet de código a seguir mostra como liberar o acesso somente leitura para arrastar dados imediatamente após a operação de arrastar e soltar. Veja um exemplo mais completo na Amostra DragAndDropAcrossApps, disponível no GitHub (em inglês).
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. } } });