O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Arrastar e soltar

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:

Visão 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 argumentos startDrag() 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 retorne true. 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ção ACTION_DRAG_ENDED. Ao enviar false, 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 o startDrag() que iniciou a operação. Espera-se que o listener retorne o valor booleano true 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ção ACTION_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 evento ACTION_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 true se ele processar o ato de soltar. Caso contrário, ele retornará false.

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 objeto View.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 como null. Se você usar esse construtor e não estender View.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 em x, e a altura aparece em y.
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 em x, e a posição Y aparece em y
onDrawShadow()
Imediatamente após a chamada para onProvideShadowMetrics(), o sistema chama onDrawShadow() para ter a sombra da ação de arrastar. O método tem um único argumento, um objeto Canvas que o sistema constrói a partir dos parâmetros fornecidos em onProvideShadowMetrics(). Use-o para desenhar a sombra da ação de arrastar no objeto Canvas 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:

  1. Se necessário, crie um ClipData e um ClipData.Item para os dados que estão sendo movidos. Como parte do objeto ClipData, forneça os metadados armazenados em um objeto ClipDescription no ClipData. Para uma operação de arrastar e soltar que não represente movimentação de dados, recomendamos usar null em vez de um objeto real.

    Por exemplo, o snippet de código a seguir mostra como responder à ação de tocar em um 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)
                    );
    
            }
        }
        
  2. O snippet de código a seguir define myDragShadowBuilder. 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 construtor View.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:

  1. Chamar getClipDescription() para ter o ClipDescription. Use os métodos do tipo MIME em ClipDescription 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 um movimento de dados, talvez isso não seja necessário.

  2. 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 envie ACTION_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:

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 ou ACTION_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 para getX() e getY(), 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 a ACTION_DRAG_ENTERED ou ACTION_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:

  1. Chamará getClipData() para receber o objeto ClipData que foi fornecido originalmente na chamada para startDrag() e o armazenará. Se a operação de arrastar e soltar não representar movimento de dados, talvez isso não seja necessário.
  2. Retornará o valor booleano true para indicar que a ação de soltar foi processada com êxito, ou o booleano false se não foi. O valor retornado se torna o valor retornado por getResult() para um evento ACTION_DRAG_ENDED.

    Observe que se o sistema não enviar um evento ACTION_DROP, o valor de getResult() para um evento ACTION_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:

  1. 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.
  2. O listener pode chamar getResult() para saber mais sobre a operação. Se um listener retornou true em resposta a um evento de tipo de ação ACTION_DROP, então getResult() retornará um booleano true. Em todos os outros casos, getResult() retorna o booleano false, incluindo qualquer caso em que o sistema não tenha enviado um evento ACTION_DROP.
  3. 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 e DRAG_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á chamar release() no objeto que foi retornado de requestDragAndDropPermissions(). 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.
                    }
                }
            });