O framework de arrastar e soltar do Android permite adicionar recursos interativos de arrastar
e soltar ao app. Com esse recurso, os usuários podem copiar ou mover texto,
imagens, objetos e qualquer conteúdo que possa ser representado por um URI, de uma
View
para outra dentro de um app 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 para permitir a transferência de dados, você pode usar o framework para outras ações da interface. 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 do documento descreve o framework de arrastar e soltar no contexto da transferência de dados.
Visão geral
Uma operação de arrastar e soltar é iniciada quando o usuário faz um gesto na interface que o app reconhece como um sinal para começar a arrastar dados. Em resposta, o app notifica o sistema de que uma operação de arrastar e soltar está sendo iniciada. O sistema chama o app para receber uma representação dos dados que estão sendo arrastados, chamada de sombra de arrastar.
À medida que o usuário move a sombra da ação de arrastar sobre o layout do app, o sistema envia eventos
de arrastar para os listeners de eventos de arrastar e métodos de callback associados aos
objetos View
no layout. Se o usuário soltar a sombra da ação de arrastar sobre uma visualização
que pode aceitar os dados (um destino de soltar), o sistema enviará os dados ao
destino. A operação de arrastar e soltar termina quando o usuário libera a sombra da ação de arrastar,
mesmo que ela esteja ou não sobre um destino de soltar.
Crie um listener de eventos de arrastar implementando
View.OnDragListener
. Defina
o listener de 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 instrui o sistema 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 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, ou seja, uma View
que aceita os dados, o sistema vai enviar um objeto de evento
de arrastar contendo os dados para o listener de eventos de arrastar do destino de soltar ou para o método
de callback.
Listeners de eventos de arrastar e métodos de callback
Um View
recebe eventos de arrastar com um listener de eventos de arrastar que implementa
View.OnDragListener
ou com o método de callback onDragEvent()
da visualização. 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 implementá-la como uma classe in-line anônima
ou expressão lambda. Para definir o listener de um objeto View
, chame
setOnDragListener()
.
Como alternativa, é possível mudar a implementação padrão de onDragEvent()
sem modificar o método. Defina um
OnReceiveContentListener
em uma visualização. Para mais detalhes, consulte
setOnReceiveContentListener()
.
Por padrão, o método onDragEvent()
faz o seguinte:
- Retorna verdadeiro em resposta à chamada para
startDragAndDrop()
. Chamar
performReceiveContent()
se os dados de arrastar e soltar forem soltos na visualização. Os dados são transmitidos para o método como um objetoContentInfo
. O método invoca oOnReceiveContentListener
.Retorna verdadeiro se os dados de arrastar e soltar forem soltos na visualização e o
OnReceiveContentListener
consumir qualquer parte do conteúdo.
Defina o OnReceiveContentListener
para processar os dados especificamente para seu
app. Para compatibilidade com versões anteriores até o nível 24 da API, use a versão do Jetpack de
OnReceiveContentListener
.
É possível 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 do método onDragEvent()
e View.OnDragListener
é
análoga à combinação de
onTouchEvent()
e View.OnTouchListener
usados com eventos de toque.
Processo de arrastar e soltar
Há quatro etapas ou estados no processo de arrastar e soltar: iniciado, continuante, solto e encerrado.
- Iniciada
Em resposta ao gesto de arrastar de um usuário, seu aplicativo chama
startDragAndDrop()
para instruir o sistema a iniciar uma operação de arrastar e soltar. Os argumentos do método fornecem o seguinte:- Os dados a serem arrastados.
- Um callback para mostrar a ação de arrastar
- Metadados que descrevem os dados arrastados:
o sistema responde chamando o aplicativo de volta para receber uma sombra
da ação de arrastar. Em seguida, o sistema mostra a ação de arrastar no dispositivo.
: Em seguida, o sistema envia um evento de arrastar com o tipo de ação
ACTION_DRAG_STARTED
ao listener de eventos de arrastar de todos os objetosView
no layout atual. Para continuar recebendo eventos de arrastar, incluindo um possível evento de soltar, o listener de eventos de arrastar precisa retornartrue
. Isso registra o listener no sistema. Somente listeners registrados continuam a receber eventos de arrastar. Nesse 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 retornarfalse
, 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 retornarfalse
, o listener informa ao sistema que não está interessado na operação de arrastar e soltar e não quer aceitar os dados arrastados.
- Em andamento
- O usuário continua a arrastar. À medida que a sombra da 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 mudar a aparência do
destino de soltar
View
em resposta ao evento. Por exemplo, se o evento indicar que a sombra da ação de arrastar entra na caixa delimitadora do destino de soltar (tipo de açãoACTION_DRAG_ENTERED
), o listener pode reagir destacando oView
. - Solto
- O usuário libera a sombra da 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 objeto de evento de arrastar contém os dados que são transmitidos ao sistema na chamadastartDragAndDrop()
que inicia a operação. Espera-se que o listener retorne o booleanotrue
ao sistema se processar com êxito os dados descartados. : esta etapa só vai ocorrer se o usuário soltar a sombra da ação de arrastar dentro da caixa delimitadora de umaView
com um listener registrado para receber eventos de arrastar (um destino de soltar). Se o usuário soltar a sombra da ação de arrastar em qualquer outra situação, nenhum evento de arrastarACTION_DROP
será enviado. - Encerrado
Após o usuário soltar a sombra da ação de arrastar e após o sistema enviar
um evento de arrastar com o tipo de ação
ACTION_DROP
. Se necessário, o sistema vai enviar 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 independentemente de onde o usuário solta a sombra da ação de arrastar. O evento é enviado a todos os listeners registrados para receber eventos de arrastar, mesmo que o listener também receba o eventoACTION_DROP
.
Cada uma dessas etapas é descrita em mais detalhes na seção 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 está acontecendo 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
,
que são descritos na tabela 1:
Tipo de ação | Significado |
---|---|
ACTION_DRAG_STARTED |
O aplicativo chama startDragAndDrop() e recebe
uma sombra de arraste. Se o listener quiser continuar recebendo eventos de arrastar
para a operação, será necessário retornar o booleano true ao
sistema.
|
ACTION_DRAG_ENTERED |
A sombra de arraste entra 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 sombra da ação de arrastar entra na caixa delimitadora.
|
ACTION_DRAG_LOCATION |
Depois de um
evento ACTION_DRAG_ENTERED , a sombra da ação de arrastar ainda
está dentro da caixa delimitadora do View do listener de eventos de
arrastar.
|
ACTION_DRAG_EXITED |
Após um ACTION_DRAG_ENTERED e pelo menos um
evento ACTION_DRAG_LOCATION , a sombra da ação de arrastar se move
para fora da caixa delimitadora do View do listener de eventos
de arrastar.
|
ACTION_DROP |
A sombra de arraste é liberada sobre a View do listener de eventos de arrastar. Esse tipo de ação é enviado ao listener de um objeto View
somente se o listener retornar o booleano
true em resposta ao
evento de arrastar ACTION_DRAG_STARTED . Esse tipo de ação não será
enviado se o usuário liberar a sombra da ação de arrastar sobre uma View
cujo listener não está registrado ou se o usuário liberar a
sombra sobre algo que não faz parte do layout atual.
O listener retornará o valor booleano |
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 enviar uma ACTION_DROP , o recebimento do
tipo de ação ACTION_DRAG_ENDED não implica que a
ação de soltar foi concluída. O listener precisa chamar
getResult() ,
conforme mostrado na Tabela 2, para receber o valor
retornado em resposta a ACTION_DROP . Se um evento ACTION_DROP não for enviado, getResult() retornará false .
|
O objeto DragEvent
também contém os dados e metadados que o aplicativo
fornece ao sistema na chamada para 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 a seção Uma
operação de arrastar e soltar.
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 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 vai retornar
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 sombra de arrastar. Você pode criá-la com 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 definidos em
View.DragShadowBuilder
para acessar 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 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 umaView
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ê tem uma sombra de arrastar com a mesma aparência daView
transmitida como argumento, centrada no local em que o usuário toca na tela.View.DragShadowBuilder()
Se você usar esse construtor, nenhum objeto
View
vai estar disponível no objetoView.DragShadowBuilder
. O campo está definido comonull
. É necessário estenderView.DragShadowBuilder
e substituir os métodos. Caso contrário, uma ação de arrastar invisível vai aparecer. O sistema não gera um erro.
A classe View.DragShadowBuilder
tem dois métodos que, juntos, criam a sombra
da 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 objetoPoint
. A largura da sombra de arraste aparece emx
, e a altura emy
.outShadowTouchPoint
:um objetoPoint
. O ponto de contato é o local dentro da sombra da ação de arrastar que precisa estar sob o dedo do usuário durante a ação. Sua posição X vai emx
, e sua posição Y vai 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 usando os parâmetros fornecidos emonProvideShadowMetrics()
. O método desenha a sombra da ação de arrastar noCanvas
fornecido.
Para melhorar o desempenho, mantenha o tamanho da sombra da ação de arrastar pequeno. Para um único item, você pode usar um ícone. Para uma seleção de vários itens, convém usar ícones em uma pilha em vez de imagens completas espalhadas pela tela.
Uma operação de arrastar e soltar
Esta seção mostra em detalhes 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, geralmente tocar e manter pressionado, em um objeto
View
. Em resposta, seu app precisa fazer o seguinte:
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 represente 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(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. val myShadow = MyDragShadowBuilder(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. 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 is handled. return true; });
Defina
myDragShadowBuilder
substituindo os métodos emView.DragShadowBuilder
. O snippet de código abaixo cria uma sombra de arrastar pequena, retangular e cinza para umaTextView
: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); } }
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 listeners reagem chamando 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 listener de eventos
de arrastar precisa fazer o seguinte:
Chame
DragEvent.getClipDescription()
e use 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 poderá ser desnecessário.
Se o listener de eventos de arrastar puder aceitar uma ação de soltar, ele precisará retornar
true
para informar ao sistema para continuar a enviar eventos de arrastar ao listener. Se o listener não puder aceitar uma ação de soltar, ele precisará retornarfalse
, e o sistema vai parar de 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 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 de eventos de arrastar que retornam true
em resposta ao
evento de arrastar ACTION_DRAG_STARTED
continuam 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 de arrastar e da visibilidade da View
do listener. Os listeners usam os eventos
de arrastar principalmente para decidir se precisam mudar a aparência das 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 ponto na tela sob o dedo ou o mouse do usuário) entra na caixa delimitadora doView
do listener.ACTION_DRAG_LOCATION
: quando o listener recebe um eventoACTION_DRAG_ENTERED
, ele recebe um novo eventoACTION_DRAG_LOCATION
sempre que o ponto de contato se move até receber um eventoACTION_DRAG_EXITED
. 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 a um listener que já 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 para o 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 ao local do ponto de contato. O listener pode usar essas informações para mudar a aparência daView
no ponto de contato ou determinar a posição exata em que o usuário pode soltar a sombra da ação de arrastar, ou seja, soltar os dados. - Em resposta a
ACTION_DRAG_EXITED
, o listener precisa redefinir todas as mudanças de aparência aplicadas 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 libera a sombra da ação de arrastar sobre uma View
e a View
informa anteriormente
que pode aceitar o conteúdo que está sendo arrastado, o sistema envia um
evento de arrastar para a 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
fornecido originalmente na chamada destartDragAndDrop()
e processe os dados. Se a operação de arrastar e soltar não representar movimento de dados, isso será desnecessário.Retorna 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
seráfalse
.
Para um evento ACTION_DROP
, getX()
e getY()
usam o sistema de coordenadas da
View
que recebe a queda para retornar as posições X e Y do
ponto de contato no momento da queda.
O sistema permite que o usuário solte a sombra da ação de arrastar sobre uma View
cujo listener de
eventos de arrastar não está recebendo eventos de arrastar. Ele também permite que o usuário solte a sombra
da ação de arrastar sobre regiões vazias da interface do aplicativo ou sobre áreas fora do
aplicativo. 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 sombra da ação de arrastar, o sistema envia um evento de arrastar
com um tipo de ação de ACTION_DRAG_ENDED
para todos os listeners de eventos de arrastar
no seu 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 mudar a aparência do objeto
View
durante a operação, ele precisará 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 retornartrue
em resposta a um evento do tipo de açãoACTION_DROP
,getResult()
retornará o booleanotrue
. Em todos os outros casos,getResult()
retorna o booleanofalse
, inclusive quando o sistema não envia um eventoACTION_DROP
. - Para indicar que a operação de arrastar e soltar foi concluída, o
listener precisa retornar 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 -> // 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; });
Arrastar e soltar no modo de várias janelas
Os dispositivos com o Android 7.0 (nível 24 da API) ou versões mais recentes oferecem suporte ao modo de várias janelas, que permite que os usuários movam dados de um app para outro usando uma operação de arrastar e soltar. Para mais informações, consulte Suporte a várias janelas.
O app de origem, em que a operação de arrastar e soltar é iniciada, fornece os dados. O app de destino, em que a operação de arrastar e soltar termina, recebe os dados.
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 se movem pelos limites do app, eles compartilham o acesso aos dados usando um URI de conteúdo. Isso exige o seguinte:
- O app de origem precisa definir uma ou ambas as flags
DRAG_FLAG_GLOBAL_URI_READ
eDRAG_FLAG_GLOBAL_URI_WRITE
, dependendo do acesso de leitura ou gravação aos dados que o app de origem quer conceder ao app de destino. - O app de destino precisa chamar
requestDragAndDropPermissions()
imediatamente antes de processar os dados que o usuário arrasta para o app. Se o app de destino não precisar mais de acesso aos dados de arrastar e soltar, ele poderá chamarrelease()
no objeto retornado derequestDragAndDropPermissions()
. Caso contrário, as permissões são liberadas quando a atividade que as contém for destruída. Se a implementação envolver o início de uma nova atividade para processar os URIs descartados, você precisará conceder as mesmas permissões à nova atividade. Você precisa definir os dados de clipe e uma sinalização:Kotlin
intent.setClipData(clipData) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
Java
intent.setClipData(clipData); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Os snippets de código abaixo demonstram como liberar o acesso somente leitura para arrastar e soltar dados imediatamente após a operação de arrastar e soltar. Consulte o exemplo DragAndDrop (link em inglês) no GitHub para ver um exemplo mais completo.
Atividade de arrastar e soltar de origem
Kotlin
// Drag a file stored in an images/ directory in internal storage. 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 permit 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 in 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 permit 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 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 afterward because it's no // longer needed. dropPermissions.release() return@setOnDragListener true } // Implement logic for other DragEvent cases here. // An unknown action type is 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 afterward 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.
Especificar destinos de soltar
DropHelper.configureView()
é um método estático e sobrecarregado que permite especificar destinos de soltar. Os
parâmetros incluem:
- O
Activity
atual, usado para permissões de URI.- Uma
View
que serve como destino de soltar.- Os tipos MIME que o destino de soltar pode aceitar dos dados soltos.
- Uma
- Opções de configuração para o destino de soltar, especificamente 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 do 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 componentes EditText
está vazia. Consulte a próxima seção para mais informações.
Configurar destinos de soltar
A classe interna DropHelper.Options
permite configurar destinos de soltar. Forneça uma instância da classe para
o método
DropHelper.configureView(Activity, View, String[], Options,
OnReceiveContentListener
. Consulte a seção anterior para saber mais.
Personalizar o destaque do destino de soltar
O DropHelper
configura destinos de soltar para exibir um destaque enquanto os usuários arrastam
o conteúdo sobre os destinos. DropHelper
oferece um estilo padrão, e DropHelper.Options
permite 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 DropHelper.Options
e definir opções de configuração,
conforme mostrado no exemplo a seguir:
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();
Processar componentes EditText em destinos de soltar
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 para
DropHelper.Options.Builder.addInnerEditTexts(EditText...)
para garantir que o destaque do destino de soltar e o processamento de dados de texto funcionem corretamente.
DropHelper
impede que componentes EditText
na hierarquia de visualização
do destino de soltar roubem o foco da visualização que a contém 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 na
seguinte ordem de precedência:
- O
EditText
em que oClipData
é solto. - O
EditText
que contém o cursor de texto (circunflexo). - O primeiro
EditText
fornecido à chamada paraDropHelper.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();
Processar dados em destinos 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
.
Dados de texto estão presentes no objeto. As mídias, como imagens, são representadas por
URIs.
O OnReceiveContentListener
também processa 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 a DropHelper.configureView()
é usada para configurar estes
tipos de visualização:
- Todas as visualizações, se o usuário estiver usando o Android 12 ou mais recente.
AppCompatEditText
, se o usuário estiver executando uma versão do Android anterior à 7.0.
Tipos MIME, permissões e validação de conteúdo
A verificação do tipo MIME feito por DropHelper
é baseada na ação de arrastar e soltar
ClipDescription
, que é
criada pelo app que fornece os dados de arrastar e soltar. Valide o
ClipDescription
para garantir que os tipos MIME estejam definidos corretamente.
DropHelper
solicita todas as permissões de acesso aos URIs de conteúdo contidos no
ClipData
de arrastar e soltar. Para saber mais, consulte
DragAndDropPermissions
. As
permissões permitem resolver 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. Verifique se há nulo e confirme a exatidão dos
dados resolvidos.