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:
- Experiência na criação de apps Android.
- Experiência com Atividades, Fragmentos, Vinculação de visualizações e layouts xml.
O que você vai fazer
Criar um app simples que:
- Implementa a funcionalidade de arrastar e soltar usando o
DragStartHelper
e oDropHelper
- 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
- Android Studio Jellyfish ou versão mais recente
- Dispositivo ou emulador do Android
2. Um evento de arrastar e soltar
Um processo de arrastar e soltar pode ser considerado um evento de quatro etapas, sendo elas:
- Iniciado: o sistema inicia a operação de arrastar e soltar em resposta ao gesto de arrastar do usuário.
- 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.
- Concluído: o usuário libera a ação de arrastar dentro da caixa delimitadora na área da ação de soltar.
- 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:
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.ClipData
: dados sendo arrastados, encapsulados no objetoClipData
.ClipDescription
: metainformações sobre o objetoClipData
.Result
: resultado da operação de arrastar e soltar.X
: coordenada x do local atual do objeto arrastado.Y
: coordenada y do local atual do objeto arrastado.
3. Configurar
Crie um novo projeto e selecione o modelo "Empty Views Activity":
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.
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
- Rótulo: texto simples para mostrar ao usuário o que está sendo arrastado
- Tipo MIME: o MimeType dos itens que estão sendo arrastados.
- 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
- data: dados arrastados na forma de
ClipData.
. - shadowBuilder: DragShadowBuilder para criar a ação de arrastar a visualização.
- 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().
- 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.
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.
- Visualização que recebeu o evento de arrastar
- 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
ACTION_DRAG_STARTED
: sinaliza o início da operação de arrastar e soltar.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.ACTION_DRAG_ENTERED
: significa que a visualização arrastada está dentro dos limites de soltar.ACTION_DROP
: significa que o usuário liberou a ação de arrastar na área de soltar.ACTION_DRAG_ENDED
: significa que a operação de arrastar e soltar foi concluída.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
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
- Activity: a atividade atual
- dropTarget: a visualização que está sendo configurada
- mimeTypes : os mimeTypes dos itens de dados da ação de soltar
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
- View: onde o conteúdo será solto
- 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.
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.
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
- Visualização atual que está recebendo os dados
- 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.
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
- Codelab para arrastar e soltar no Compose
- Ativar o recurso de arrastar e soltar
- Amostra de arrastar e soltar (link em inglês)
- Repositório de exemplos de codelab (link em inglês)
- Como criar aplicativos para dispositivos dobráveis
- Suporte a várias janelas
- Layouts responsivos para o desenvolvimento de telas grandes
- Jetpack WindowManager
- Exemplo da Jetpack WindowManager (link em inglês)
- Desenvolver apps para dispositivos de tela dupla