Codelab para arrastar e soltar

1. Antes de começar

Este codelab oferece instruções práticas sobre a implementação da funcionalidade de arrastar e soltar para visualizações. Você aprenderá a ativar o recurso de arrastar e soltar visualizações no seu app e entre diferentes apps, e irá implementar interações de arrastar e soltar no seu app e até entre diferentes apps. O codelab vai orientar você no uso do DropHelper para ativar o recurso de arrastar e soltar, personalizar o feedback visual durante a ação de arrastar com o ShadowBuilder, adicionar permissões para arrastar entre apps e implementar um receptor de conteúdo que funcione universalmente.

Pré-requisitos

Para concluir este codelab, você precisa ter:

O que você vai fazer

Criar um app simples que:

  • Implementa a funcionalidade de arrastar e soltar usando o DragStartHelper e o DropHelper
  • Muda o ShadowBuilder
  • Adiciona permissão para arrastar entre apps
  • Implementa o receptor de conteúdo avançado para aplicação universal

O que é necessário

2. Um evento de arrastar e soltar

Um processo de arrastar e soltar pode ser considerado um evento de quatro etapas, sendo elas:

  1. Iniciado: o sistema inicia a operação de arrastar e soltar em resposta ao gesto de arrastar do usuário.
  2. Em andamento: o usuário continua a operação e o builder da ação de arrastar é iniciado quando entra na visualização de destino.
  3. Concluído: o usuário libera a ação de arrastar dentro da caixa delimitadora na área da ação de soltar.
  4. Encerrado: o sistema envia o sinal para encerrar a operação de arrastar e soltar.

O sistema envia o evento de arrastar no objeto DragEvent. O objeto DragEvent pode conter estes dados:

  1. ActionType: valor da ação do evento com base no ciclo de vida de arrastar e soltar. Por exemplo, ACTION_DRAG_STARTED, ACTION_DROP etc.
  2. ClipData: dados sendo arrastados, encapsulados no objeto ClipData.
  3. ClipDescription: metainformações sobre o objeto ClipData.
  4. Result: resultado da operação de arrastar e soltar.
  5. X: coordenada x do local atual do objeto arrastado.
  6. Y: coordenada y do local atual do objeto arrastado.

3. Configurar

Crie um novo projeto e selecione o modelo "Empty Views Activity":

2fbd2bca1483033f.png

Deixe todos os parâmetros como padrão. Deixe o projeto ser sincronizado e indexado. O elemento MainActivity.kt foi criado com a visualização activity_main.xml

4. Arrastar e soltar usando visualizações

No string.xml, vamos adicionar alguns valores de string

<resources>
    <string name="app_name">DragAndDropCodelab</string>
    <string name="drag_image">Drag Image</string>
    <string name="drop_image">drop image</string>
 </resources>

Abra o arquivo de origem activity_main.xml e modifique o layout para incluir dois ImageViews, um vai funcionar como origem da ação de arrastar e o outro como destino de soltar.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_greeting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@id/iv_source"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/iv_source"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/drag_image"
        app:layout_constraintBottom_toTopOf="@id/iv_target"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_greeting" />

    <ImageView
        android:id="@+id/iv_target"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/drop_image"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>

Em build.gradle.kts, ative a vinculação de visualizações

buildFeatures{
   viewBinding = true
}

Em build.gradle.kts, adicione uma dependência do Glide

dependencies {
    implementation("com.github.bumptech.glide:glide:4.16.0")
    annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")

    //other dependencies
}

Adicione URLs de imagem e texto de saudação em string.xml

<string name="greeting">Drag and Drop</string>
<string name="target_url">https://services.google.com/fh/files/misc/qq2.jpeg</string>
<string name="source_url">https://services.google.com/fh/files/misc/qq10.jpeg</string>

Em MainActivity.kt, vamos inicializar as visualizações.

class MainActivity : AppCompatActivity() {
   val binding by lazy(LazyThreadSafetyMode.NONE) {
       ActivityMainBinding.inflate(layoutInflater)
   }

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       binding.tvGreeting.text = getString(R.string.greeting)
       Glide.with(this).asBitmap()
           .load(getString(R.string.source_url))
           .into(binding.ivSource)
       Glide.with(this).asBitmap()
           .load(getString(R.string.target_url))
           .into(binding.ivTarget)
   }
}

Nesse estado, seu app precisa mostrar o texto de boas-vindas e duas imagens na orientação vertical.

b0e651aaee336750.png

5. Tornar a visualização arrastável

Para tornar uma visualização específica arrastável, ela precisa implementar o método startDragAndDrop() no gesto de arrastar.

Vamos implementar um callback para onLongClickListener quando o usuário iniciar a ação de arrastar na visualização.

draggableView.setOnLongClickListener{ v ->
   //drag logic here
   true
}

Mesmo que a visualização não aceite o clique longo, vai aceitar com esse callback. O valor de retorno é booleano. "True" (verdadeiro) significa que a ação de arrastar é consumida pelo callback.

Preparar ClipData: dados a serem arrastados

Vamos definir os dados que queremos soltar. Os dados podem ser de qualquer tipo, de texto simples a vídeo. Esses dados são encapsulados no objeto ClipData. O objeto ClipData contêm um ou mais objetos ClipItem complexos

Com diferentes tipos MIME definidos em ClipDescription.

Estamos arrastando o URL da imagem da visualização de origem. Há três componentes principais de ClipData

  1. Rótulo: texto simples para mostrar ao usuário o que está sendo arrastado
  2. Tipo MIME: o MimeType dos itens que estão sendo arrastados.
  3. ClipItem: item a ser arrastado e encapsulado no objeto ClipData.Item.

Vamos criar ClipData.

val label = "Dragged Image Url"
val clipItem = ClipData.Item(v.tag as? CharSequence)
val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
val draggedData = ClipData(
   label, mimeTypes, clipItem
)

Iniciar a ação de arrastar e soltar

Agora que temos os dados para arrastar, vamos iniciar a operação. Para isso, vamos usar startDragAndDrop.

O método startDragAndDrop recebe quatro argumentos

  1. data: dados arrastados na forma de ClipData..
  2. shadowBuilder: DragShadowBuilder para criar a ação de arrastar a visualização.
  3. myLocalState: um objeto que contém dados locais sobre a operação de arrastar e soltar. Ao enviar eventos de arrastar para visualizações na mesma atividade, esse objeto ficará disponível pelo método DragEvent.getLocalState().
  4. Flags: sinalizações para controlar as operações de arrastar e soltar.

Depois que essa função é chamada, com base na classe View.DragShadowBuilder, a ação de arrastar é renderizada. Quando o sistema tiver a ação de arrastar, a operação de arrastar e soltar será iniciada enviando o evento para a visualização que implementou a interface OnDragListener.

v.startDragAndDrop(
   draggedData,
   View.DragShadowBuilder(v),
   null,
   0
)

Com isso, configuramos nossa visualização para arrastar e definimos os dados a serem arrastados. A implementação final vai ficar assim:

fun setupDrag(draggableView: View) {
   draggableView.setOnLongClickListener { v ->
       val label = "Dragged Image Url"
       val clipItem = ClipData.Item(v.tag as? CharSequence)
       val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
       val draggedData = ClipData(
           label, mimeTypes, clipItem
       )
       v.startDragAndDrop(
           draggedData,
           View.DragShadowBuilder(v),
           null,
           0
       )
   }
}

Nesta fase, você conseguirá arrastar a visualização com um clique longo.

526e9e2a7f3a90ea.gif

Agora vamos configurar a visualização solta.

6. Configurar a visualização para o DropTarget

A visualização pode atuar como destino da ação de soltar, já que implementou a interface OnDragListener.

Vamos configurar a segunda visualização de imagem para que ela seja uma ação de arrastar e soltar.

private fun setupDrop(dropTarget: View) {
   dropTarget.setOnDragListener { v, event ->
       // handle drag events here
       true
   }
}

Estamos substituindo o método onDrag da interface OnDragListener. O método onDrag tem dois argumentos.

  1. Visualização que recebeu o evento de arrastar
  2. Objeto do evento de arrastar

Esse método retorna "true" (verdadeiro) se o evento de arrastar for processado. Caso contrário, retorna "false" (falso).

DragEvent

Representa um pacote de dados transmitido pelo sistema em diferentes etapas de uma operação de arrastar e soltar. Esse pacote de dados encapsula informações vitais sobre a operação em si e os dados envolvidos.

O DragEvent tem diferentes ações de arrastar com base no estágio da operação de arrastar e soltar

  1. ACTION_DRAG_STARTED: sinaliza o início da operação de arrastar e soltar.
  2. ACTION _DRAG_LOCATION: significa que o usuário liberou a ação de arrastar no estado inserido, ou seja, não está dentro do limite da área de soltar.
  3. ACTION_DRAG_ENTERED: significa que a visualização arrastada está dentro dos limites de soltar.
  4. ACTION_DROP: significa que o usuário liberou a ação de arrastar na área de soltar.
  5. ACTION_DRAG_ENDED: significa que a operação de arrastar e soltar foi concluída.
  6. ACTION_DRAG_EXITED: significa o fim da operação de arrastar e soltar.

Validar o DragEvent

Você pode continuar com a operação de arrastar e soltar se todas as restrições forem atendidas no evento ACTION_DRAG_STARTED. Neste exemplo, podemos conferir se os dados recebidos são do tipo correto ou não.

DragEvent.ACTION_DRAG_STARTED -> {
   Log.d(TAG, "ON DRAG STARTED")
   if (event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
       (v as? ImageView)?.alpha = 0.5F
       v.invalidate()
       true
   } else {
       false
   }
}

Neste exemplo, conferimos se o ClipDescription do evento tem o Tipo MIME aceitável ou não. Em caso afirmativo, fornecemos o sinal visual indicativo e retornamos "true" para indicar que os dados arrastados estão sendo processados. Caso contrário, retornamos "false" para indicar que a ação de arrastar está sendo descartada pela ação de arrastar e soltar.

Processar dados de soltar

No evento ACTION_DROP, podemos escolher o que fazer com os dados soltos. Neste exemplo, extraímos o URL que adicionamos ao ClipData como texto. Colocamos esta imagem do URL na visualização da imagem de destino

DragEvent.ACTION_DROP -> {
   Log.d(TAG, "On DROP")
   val item: ClipData.Item = event.clipData.getItemAt(0)
   val dragData = item.text
   Glide.with(this).load(item.text).into(v as ImageView)
   (v as? ImageView)?.alpha = 1.0F
   true
}

Além de processar a ação de soltar, podemos configurar o que acontece quando um usuário arrasta a visualização na caixa delimitadora da ação de arrastar e soltar e o que acontece quando o usuário arrasta para fora da área de soltar.

Vamos adicionar algumas indicações visuais quando o item arrastado entra na área de destino

DragEvent.ACTION_DRAG_ENTERED -> {
   Log.d(TAG, "ON DRAG ENTERED")
   (v as? ImageView)?.alpha = 0.3F
   v.invalidate()
   true
}

Além disso, adicione mais indicações visuais quando o usuário arrastar a visualização para fora da caixa delimitadora da ação de arrastar e soltar.

DragEvent.ACTION_DRAG_EXITED -> {
   Log.d(TAG, "ON DRAG EXISTED")
   (v as? ImageView)?.alpha = 0.5F
   v.invalidate()
   true
}

Adicione mais algumas indicações visuais para marcar o fim da operação de arrastar e soltar

DragEvent.ACTION_DRAG_ENDED -> {
   Log.d(TAG, "ON DRAG ENDED")
   (v as? ImageView)?.alpha = 1.0F
   true
}

Nesta fase, você consegue arrastar uma imagem para a área de destino. Quando soltar, a imagem da ImageView vai refletir a mudança

114238f666d84c6f.gif

7. Arrastar e soltar no modo de várias janelas

Os itens podem ser arrastados de um app para outro se estiverem compartilhando a tela pelo modo de várias janelas. A implementação para ativar o recurso de arrastar e soltar entre apps é a mesma, mas é preciso adicionar flags durante a ação de arrastar e permissões durante a ação de soltar.

Configurar flags durante a ação de arrastar

Como abordamos, startDragAndDrop tem um argumento para especificar as flags, que controlam a operação de arrastar e soltar.

v.startDragAndDrop(
   draggedData,
   View.DragShadowBuilder(v),
   null,
   View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
)

View.DRAG_FLAG_GLOBAL significa que a ação de arrastar pode cruzar os limites da janela, e View.DRAG_FLAG_GLOBAL_URI_READ significa que o destinatário da ação de arrastar consegue ler os URIs de conteúdo.

Para que a ação de soltar leia dados arrastados de outros apps, a visualização dessa ação precisa declarar a permissão de leitura.

val dropPermission = requestDragAndDropPermissions(event)

Libere a permissão depois que os dados arrastados forem processados.

dropPermission.release()

O processamento final do item arrastado fica assim:

DragEvent.ACTION_DROP -> {
   Log.d(TAG, "On DROP")
   val dropPermission = requestDragAndDropPermissions(event)
   val item: ClipData.Item = event.clipData.getItemAt(0)
   val dragData = item.text
   Glide.with(this).load(item.text).into(v as ImageView)
   (v as? ImageView)?.alpha = 1.0F
   dropPermission.release()
   true
}

Nessa fase, você consegue arrastar essa imagem para outro app e dados arrastados de outro app podem ser processados corretamente.

8. Biblioteca de arrastar e soltar

O Jetpack oferece uma biblioteca DragAndDrop para simplificar a implementação da operação de arrastar e soltar.

Vamos adicionar uma dependência ao arquivo build.gradle.kts para usar a biblioteca DragAndDrop.

implementation("androidx.draganddrop:draganddrop:1.0.0")

Para este exercício, crie uma atividade separada chamada DndHelperActivity.kt, que tem duas ImageViews na vertical. Uma delas vai atuar como a ação de arrastar e a outra será a ação de soltar.

Modifique strings.xml para adicionar recursos de string.

<string name="greeting_1">DragStartHelper and DropHelper</string>
<string name="target_url_1">https://services.google.com/fh/files/misc/qq9.jpeg</string>
<string name="source_url_1">https://services.google.com/fh/files/misc/qq8.jpeg</string>

Atualize o arquivo activity_dnd_helper.xml para incluir ImageViews

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="24dp"
   tools:context=".DnDHelperActivity">

   <TextView
       android:id="@+id/tv_greeting"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toTopOf="@id/iv_source"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <ImageView
       android:id="@+id/iv_source"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drag_image"
       app:layout_constraintBottom_toTopOf="@id/iv_target"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/tv_greeting" />

   <ImageView
       android:id="@+id/iv_target"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drop_image"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>

Por fim, inicialize as visualizações em DnDHelperActivity.kt

class DnDHelperActivity : AppCompatActivity() {
   private val binding by lazy(LazyThreadSafetyMode.NONE) {
       ActivityMainBinding.inflate(layoutInflater)
   }

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       binding.tvGreeting.text = getString(R.string.greeting)
       Glide.with(this).asBitmap()
           .load(getString(R.string.source_url_1))
           .into(binding.ivSource)
       Glide.with(this).asBitmap()
           .load(getString(R.string.target_url_1))
           .into(binding.ivTarget)
       binding.ivSource.tag = getString(R.string.source_url_1)
   }
}

Atualize o AndroidManifest.xml para tornar a DndHelperActivity uma atividade da tela de início

<activity
   android:name=".DnDHelperActivity"
   android:exported="true">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
</activity>

DragStartHelper

Anteriormente, configuramos a visualização para ser arrastável implementando onLongClickListener e chamando startDragAndDrop. DragStartHelper simplifica a implementação fornecendo métodos utilitários.

DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
   // prepare clipData

   // startDrag and Drop
}.attach()

DragStartHelper usa a visualização que será arrastada como argumento. Aqui, implementamos o método OnDragStartListener onde vamos preparar o clipdata e iniciar a operação de arrastar e soltar.

A implementação final vai ficar assim:

DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
   val item = ClipData.Item(view.tag as? CharSequence)
   val dragData = ClipData(
       view.tag as? CharSequence,
       arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
       item
   )
   view.startDragAndDrop(
       dragData,
       View.DragShadowBuilder(view),
       null,
       0
   )
}.attach()

DropHelper

O DropHelper simplifica a configuração da visualização da ação de soltar fornecendo um método utilitário chamado configureView.

O método configureView usa quatro argumentos

  1. Activity: a atividade atual
  2. dropTarget: a visualização que está sendo configurada
  3. mimeTypes : os mimeTypes dos itens de dados da ação de soltar
  4. OnReceiveContentListener: interface para processar os dados na ação de soltar

Personalizar os destaques da ação de soltar.

DropHelper.configureView(
   This, // Current Activity
   dropTarget,
   arrayOf("text/*"),
   DropHelper.Options.Builder().build()
) {
   // handle the dropped data
}

O OnReceiveContentListener recebe o conteúdo da ação de soltar. Ele tem dois parâmetros

  1. View: onde o conteúdo será solto
  2. Payload: o conteúdo real a ser solto
private fun setupDrop(dropTarget: View) {
   DropHelper.configureView(
       this,
       dropTarget,
       arrayOf("text/*"),
   ) { _, payload: ContentInfoCompat ->
       // TODO: step through clips if one cannot be loaded
       val item = payload.clip.getItemAt(0)
       val dragData = item.text
       Glide.with(this)
           .load(dragData)
           .centerCrop().into(dropTarget as ImageView)
       // Consume payload by only returning remaining items
       val (_, remaining) = payload.partition { it == item }
       remaining
   }
}

Neste estágio, você consegue arrastar e soltar dados usando o DragStartHelper e o DropHelper.

2e32d6cd80e19dcb.gif

Configurar os destaques da área de soltar

Como você pôde notar, quando um item arrastado entra na área de soltar, ela é destacada. Usando DropHelper.Options, podemos personalizar como a área de soltar é destacada quando um item arrastado entra nos limites da visualização.

As DropHelper.Options podem ser usadas para configurar a cor de destaque e o raio do canto de destaque da área da ação de soltar.

DropHelper.Options.Builder()
   .setHighlightColor(getColor(R.color.green))
   .setHighlightCornerRadiusPx(16)
   .build()

Essas opções precisam ser transmitidas como argumentos para o método configureView do DropHelper.

private fun setupDrop(dropTarget: View) {
   DropHelper.configureView(
       this,
       dropTarget,
       arrayOf("text/*"),
       DropHelper.Options.Builder()
           .setHighlightColor(getColor(R.color.green))
           .setHighlightCornerRadiusPx(16)
           .build(),
   ) { _, payload: ContentInfoCompat ->
       // TODO: step through clips if one cannot be loaded
       val item = payload.clip.getItemAt(0)
       val dragData = item.text
       Glide.with(this)
           .load(dragData)
           .centerCrop().into(dropTarget as ImageView)
       // Consume payload by only returning remaining items
       val (_, remaining) = payload.partition { it == item }
       remaining
   }
}

É possível conferir a cor e o raio de destaque durante a ação de arrastar e soltar.

9d5c1c78ecf8575f.gif

9. Receber conteúdo avançado

OnReceiveContentListener é a API unificada para receber conteúdo avançado, incluindo texto, html, imagens, vídeos etc. O conteúdo pode ser inserido nas visualizações usando o teclado, a área de transferência, ou arrastando. Manter o callback para cada mecanismo de entrada pode ser incômodo. O OnReceiveContentListener pode ser usado para receber conteúdo como texto, marcações, áudio, vídeo, imagens, entre outros, usando uma única API. A API OnReceiveContentListener unificada consolida esses diferentes caminhos de código, criando uma única API para implementação. Isso permite que você se concentre na lógica específica do app e deixa a plataforma cuidar do restante.

Para este exercício, crie uma atividade separada chamada ReceiveRichContentActivity.kt, que tem duas ImageViews na vertical. Uma delas vai atuar como a ação de arrastar e a outra será a ação de soltar.

Modifique strings.xml para adicionar recursos de string.

<string name="greeting_2">Rich Content Receiver</string>
<string name="target_url_2">https://services.google.com/fh/files/misc/qq1.jpeg</string>
<string name="source_url_2">https://services.google.com/fh/files/misc/qq3.jpeg</string>

Atualize activity_receive_rich_content.xml para incluir ImageViews.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".ReceiveRichContentActivity">

   <TextView
       android:id="@+id/tv_greeting"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toTopOf="@id/iv_source"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <ImageView
       android:id="@+id/iv_source"
       android:layout_width="320dp"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drag_image"
       app:layout_constraintBottom_toTopOf="@id/iv_target"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/tv_greeting" />

   <ImageView
       android:id="@+id/iv_target"
       android:layout_width="320dp"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drop_image"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>

Por fim, inicialize as visualizações em ReceiveRichContentActivity.kt.

class ReceiveRichContentActivity : AppCompatActivity() {
   private val binding by lazy(LazyThreadSafetyMode.NONE) {
       ActivityReceiveRichContentBinding.inflate(layoutInflater)
   }
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       binding.tvGreeting.text = getString(R.string.greeting_2)
       Glide.with(this).asBitmap()
           .load(getString(R.string.source_url_2))
           .into(binding.ivSource)
       Glide.with(this).asBitmap()
           .load(getString(R.string.target_url_2))
           .into(binding.ivTarget)
       binding.ivSource.tag = getString(R.string.source_url_2)
   }
}

Atualize o AndroidManifest.xml para definir DndHelperActivity como a atividade da tela de início.

<activity
   android:name=".ReceiveRichContentActivity"
   android:exported="true">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
</activity>

Primeiro, vamos criar um callback que implemente o OnReceiveContentListener..

val listener = OnReceiveContentListener { view, payload ->
   val (textContent, remaining) =
       payload.partition { item: ClipData.Item -> item.text != null }
   if (textContent != null) {
       val clip = textContent.clip
       for (i in 0 until clip.itemCount) {
           val currentText = clip.getItemAt(i).text
           Glide.with(this)
               .load(currentText)
               .centerCrop().into(view as ImageView)
       }
   }
   remaining
}

Aqui, implementamos a interface OnRecieveContentListener. O método onRecieveContent tem dois argumentos

  1. Visualização atual que está recebendo os dados
  2. Payload de dados do teclado, da área de transferência ou que estão sendo arrastados na forma de ContentInfoCompat

Esse método retorna o payload que não é processado.

Aqui, separamos o payload em conteúdo de texto e outros conteúdos usando o método de partição. Estamos processando os dados de texto de acordo com nossas necessidades e retornamos o payload restante.

Vamos processar o que queremos fazer com os dados arrastados.

val listener = OnReceiveContentListener { view, payload ->
   val (textContent, remaining) =
       payload.partition { item: ClipData.Item -> item.text != null }
   if (textContent != null) {
       val clip = textContent.clip
       for (i in 0 until clip.itemCount) {
           val currentText = clip.getItemAt(i).text
           Glide.with(this)
               .load(currentText)
               .centerCrop().into(view as ImageView)
       }
   }
   remaining
}

Agora nosso listener está pronto. Vamos adicionar esse listener à visualização de destino.

ViewCompat.setOnReceiveContentListener(
   binding.ivTarget,
   arrayOf("text/*"),
   listener
)

Nesta fase, é possível arrastar e soltar imagens na área de destino. Quando solta, a imagem arrastada substitui a imagem original na área da ação de soltar.

e4c3a3163c51135d.gif

10. Parabéns!

Agora você já sabe como implementar o recurso de arrastar e soltar no seu app Android. Com este codelab, você aprendeu a criar ações interativas de arrastar e soltar no seu app Android e em diferentes apps, melhorando a experiência do usuário e a funcionalidade. Você aprendeu

  • Conceitos básicos do recurso de arrastar e soltar: como as quatro etapas de um evento de arrastar e soltar (iniciado, em andamento, concluído, encerrado) e os principais dados dentro do objeto DragEvent.
  • Como ativar o recurso de arrastar e soltar: agora é possível arrastar e soltar processando o DragEvent.
  • Arrastar e soltar no modo de várias janelas: ative o recurso de arrastar e soltar entre apps configurando as flags e permissões adequadas.
  • Usar a biblioteca DragAndDrop: como simplificar a implementação de arrastar e soltar usando a biblioteca do Jetpack
  • Receber conteúdo avançado: como fazer a implementação para processar diversos tipos de conteúdo (texto, imagens, vídeos etc.) de vários métodos de entrada usando a API unificada.

Saiba mais