Il framework di trascinamento di Android ti consente di aggiungere funzionalità interattive di trascinamento alla tua app. Con il trascinamento, gli utenti possono copiare o spostare testo, immagini, oggetti e qualsiasi contenuto che può essere rappresentato da un URI, da un elemento View
a un altro all'interno di un'app o tra app in modalità multi-finestra.
|
|
Il framework include una classe di eventi di trascinamento, listener di trascinamento e classi e metodi helper. Sebbene sia progettato principalmente per consentire il trasferimento dei dati, puoi utilizzare il framework per altre azioni dell'interfaccia utente. Ad esempio, puoi creare un'app che mescola i colori quando l'utente trascina un'icona di un colore su un'altra. Tuttavia, il resto del documento descrive il framework a trascinamento nel contesto del trasferimento dei dati.
Panoramica
Un'operazione di trascinamento inizia quando l'utente esegue un gesto nell'interfaccia utente che la tua app riconosce come indicatore per iniziare a trascinare i dati. In risposta, l'app comunica al sistema l'avvio di un'operazione di trascinamento. Il sistema richiama l'app per ottenere una rappresentazione dei dati trascinati, chiamata ombra di trascinamento.
Quando l'utente sposta l'ombra di trascinamento sul layout dell'app, il sistema invia eventi di trascinamento ai listener di eventi di trascinamento e ai metodi di callback associati agli oggetti View
nel layout. Se l'utente rilascia l'ombra di trascinamento su una vista che può accettare i dati (una destinazione di rilascio), il sistema invia i dati alla destinazione. L'operazione di trascinamento termina quando l'utente rilascia l'ombra di trascinamento, indipendentemente dal fatto che l'ombra si trovi o meno sopra una destinazione.
Crea un listener di eventi di trascinamento implementando
View.OnDragListener
. Imposta il listener per una destinazione di rilascio con il metodo setOnDragListener()
dell'oggetto View
. Ogni vista nel layout ha anche un metodo di callback onDragEvent()
.
L'applicazione comunica al sistema di avviare un'operazione di trascinamento chiamando il metodo startDragAndDrop()
, che indica al sistema di inviare eventi di trascinamento. Il metodo fornisce inoltre al sistema i dati che l'utente sta trascinando e i metadati che li descrivono. Puoi
chiamare startDragAndDrop()
su qualsiasi View
nel layout corrente. Il sistema
utilizza l'oggetto View
solo per ottenere l'accesso alle impostazioni globali nel layout.
Durante l'operazione di trascinamento, il sistema invia gli eventi di trascinamento ai listener degli eventi di trascinamento o ai metodi di callback degli oggetti View
nel layout. Gli ascoltatori o i metodi di callback utilizzano i metadati per decidere se accettare o meno i dati quando vengono eliminati. Se l'utente rilascia i dati su una destinazione di rilascio, ovvero un View
che accetta i dati, il sistema invia un oggetto evento di trascinamento contenente i dati al metodo di callback o al listener di eventi di trascinamento della destinazione di rilascio.
Trascinare i listener di eventi e i metodi di callback
Un View
riceve eventi di trascinamento con un listener di eventi di trascinamento che implementa
View.OnDragListener
o con il metodo di callback onDragEvent()
della vista. Quando il sistema chiama il metodo o il listener, fornisce un argomento DragEvent
.
Nella maggior parte dei casi, è preferibile utilizzare un listener rispetto al metodo di callback. Quando
progetta le UI, in genere non sottoclassi View
, ma il
metodo di callback ti costringe a creare sottoclassi per sostituire il metodo. Al confronto, puoi implementare una classe listener e quindi utilizzarla con più oggetti View
diversi. Puoi implementarla anche come classe
in linea anonima o espressione lambda. Per impostare il listener per un oggetto View
, chiama setOnDragListener()
.
In alternativa, puoi modificare l'implementazione predefinita di onDragEvent()
senza eseguire l'override del metodo. Imposta un OnReceiveContentListener
su una vista. Per ulteriori dettagli, consulta setOnReceiveContentListener()
.
Per impostazione predefinita, il metodo onDragEvent()
effettua quindi le seguenti operazioni:
- Restituisce true in risposta alla chiamata a
startDragAndDrop()
. Richiama
performReceiveContent()
se i dati relativi al trascinamento vengono rilasciati nella visualizzazione. I dati vengono passati al metodo come oggettoContentInfo
. Il metodo richiamaOnReceiveContentListener
.Restituisce true se i dati di trascinamento vengono rilasciati nella visualizzazione e
OnReceiveContentListener
consuma qualsiasi contenuto.
Definisci il OnReceiveContentListener
per gestire i dati specifici per la tua app. Per la compatibilità con le versioni precedenti fino al livello API 24, utilizza la versione Jetpack di OnReceiveContentListener
.
Per un oggetto View
puoi avere un listener di eventi di trascinamento e un metodo di callback, nel qual caso il sistema chiama prima il listener. Il sistema non chiama il metodo di callback, a meno che il listener non restituisca false
.
La combinazione del metodo onDragEvent()
e di View.OnDragListener
è
analogo alla combinazione di
onTouchEvent()
e View.OnTouchListener
utilizzata con gli eventi tocco.
Procedura di trascinamento
Il processo di trascinamento prevede quattro passaggi o stati: avviato, continuo, eliminato e terminato.
- Avviato
In risposta al gesto di trascinamento di un utente, l'applicazione chiama
startDragAndDrop()
per indicare al sistema di avviare un'operazione di trascinamento. Gli argomenti del metodo forniscono quanto segue:- I dati da trascinare.
- Un callback per disegnare l'ombra di trascinamento
- Metadati che descrivono i dati trascinati: il sistema risponde richiamando l'applicazione per ottenere un'ombra. Il sistema visualizza l'ombra di trascinamento sul dispositivo.
: il sistema invia un evento di trascinamento con tipo di azione
ACTION_DRAG_STARTED
al listener di eventi di trascinamento di tutti gli oggettiView
nel layout corrente. Per continuare a ricevere eventi di trascinamento, incluso un possibile evento di trascinamento, il listener di eventi di trascinamento deve restituiretrue
. In questo modo il listener viene registrato nel sistema. Solo i listener registrati continuano a ricevere eventi di trascinamento. A questo punto, i listener possono anche modificare l'aspetto dell'oggettoView
di destinazione di rilascio per indicare che la visualizzazione può accettare un evento di rilascio. : se il listener di eventi di trascinamento restituiscefalse
, non riceve eventi di trascinamento per l'operazione corrente finché il sistema non invia un evento di trascinamento con tipo di azioneACTION_DRAG_ENDED
. Se restituiscifalse
, il listener comunica al sistema che non è interessato all'operazione di trascinamento e non vuole accettare i dati trascinati.
- Avanzamento
- L'utente continua a trascinare. Quando l'ombra di trascinamento interseca il riquadro di delimitazione di una destinazione, il sistema invia uno o più eventi di trascinamento al listener di eventi di trascinamento della destinazione. Il listener potrebbe modificare l'aspetto della destinazione di rilascio
View
in risposta all'evento. Ad esempio, se l'evento indica che l'ombra di trascinamento entra nel riquadro di delimitazione della destinazione del rilascio (tipo di azioneACTION_DRAG_ENTERED
), l'ascoltatore può reagire evidenziandoView
. - Interrotto
- L'utente rilascia l'ombra di trascinamento all'interno del riquadro di delimitazione di una destinazione. Il sistema invia al listener della destinazione di rilascio un evento di trascinamento con tipo di azione
ACTION_DROP
. L'oggetto dell'evento di trascinamento contiene i dati trasmessi al sistema nella chiamata astartDragAndDrop()
che avvia l'operazione. Il listener dovrebbe restituire il valore booleanotrue
al sistema se elabora correttamente i dati eliminati. : questo passaggio si verifica solo se l'utente rilascia l'ombra di trascinamento all'interno del riquadro di delimitazione di unView
il cui listener è registrato per ricevere eventi di trascinamento (una destinazione di rilascio). Se l'utente rilascia l'ombra di trascinamento in qualsiasi altra situazione, non viene inviato alcun evento di trascinamentoACTION_DROP
. - Terminata
Dopo che l'utente rilascia l'ombra di trascinamento e dopo che il sistema ha inviato
un evento di trascinamento con tipo di azione
ACTION_DROP
. Se necessario, il sistema invia un evento di trascinamento con tipo di azioneACTION_DRAG_ENDED
per indicare che l'operazione di trascinamento è terminata. indipendentemente dal punto in cui l'utente rilascia l'ombra di trascinamento. L'evento viene inviato a ogni listener registrato per ricevere eventi di trascinamento, anche se il listener riceve anche l'eventoACTION_DROP
.
Ciascuno di questi passaggi è descritto più dettagliatamente nella sezione Un'operazione di trascinamento.
Trascina eventi
Il sistema invia un evento di trascinamento sotto forma di oggetto DragEvent
, che contiene un tipo di azione che descrive ciò che sta accadendo nel processo di trascinamento. A seconda del tipo di azione, l'oggetto può contenere anche altri dati.
I listener di eventi di trascinamento ricevono l'oggetto DragEvent
. Per ottenere il tipo di azione,
gli ascoltatori chiamano
DragEvent.getAction()
.
Esistono sei valori possibili definiti da costanti nella classe DragEvent
,
descritti nella tabella 1:
Tipo di azione | Significato |
---|---|
ACTION_DRAG_STARTED |
L'applicazione chiama startDragAndDrop() e ottiene un'ombra di trascinamento. Se il listener vuole continuare a ricevere eventi di trascinamento per questa operazione, deve restituire il valore booleano true al sistema.
|
ACTION_DRAG_ENTERED |
L'ombra di trascinamento entra nel riquadro di delimitazione del listener di eventi di trascinamento
View . Questo è il primo tipo di azione evento che il listener riceve quando l'ombra di trascinamento entra nel riquadro di delimitazione.
|
ACTION_DRAG_LOCATION |
Dopo un evento ACTION_DRAG_ENTERED , l'ombra di trascinamento si trova ancora all'interno del riquadro di delimitazione dell'View del listener di eventi di trascinamento.
|
ACTION_DRAG_EXITED |
Dopo un evento ACTION_DRAG_ENTERED e almeno un evento ACTION_DRAG_LOCATION , l'ombra di trascinamento si sposta all'esterno del riquadro di delimitazione dell'View del listener di eventi di trascinamento.
|
ACTION_DROP |
L'ombra di trascinamento viene rilasciata su View del listener di eventi di trascinamento. Questo tipo di azione viene inviato al listener di un oggetto View solo se il listener restituisce true booleano in risposta all'evento di trascinamento ACTION_DRAG_STARTED . Questo tipo di azione non viene inviato se l'utente rilascia l'ombra di trascinamento su un View il cui listener non è registrato o se l'utente rilascia l'ombra di trascinamento su qualsiasi elemento che non fa parte del layout corrente.
Il listener restituisce |
ACTION_DRAG_ENDED |
Il sistema sta terminando l'operazione di trascinamento. Questo tipo di azione non è necessariamente preceduto da un evento ACTION_DROP . Se il sistema invia un ACTION_DROP , la ricezione del tipo di azione ACTION_DRAG_ENDED non implica che l'eliminazione sia riuscita. Il listener deve chiamare
getResult() ,
come mostrato nella tabella 2, per ottenere il valore che viene
restituito in risposta a ACTION_DROP . Se un
evento ACTION_DROP non viene inviato, getResult() restituisce false .
|
L'oggetto DragEvent
contiene anche i dati e i metadati che l'applicazione fornisce al sistema nella chiamata a startDragAndDrop()
. Alcuni dati sono validi solo per determinati tipi di azioni, come riepilogato nella tabella 2. Per ulteriori informazioni sugli eventi e sui relativi dati, consulta la sezione Un'operazione di trascinamento.
getAction() valore |
getClipDescription() valore |
getLocalState() valore |
getX() valore |
getY() valore |
getClipData() valore |
getResult() valore |
---|---|---|---|---|---|---|
ACTION_DRAG_STARTED |
✓ | ✓ | ✓ | ✓ | ||
ACTION_DRAG_ENTERED |
✓ | ✓ | ||||
ACTION_DRAG_LOCATION |
✓ | ✓ | ✓ | ✓ | ||
ACTION_DRAG_EXITED |
✓ | ✓ | ||||
ACTION_DROP |
✓ | ✓ | ✓ | ✓ | ✓ | |
ACTION_DRAG_ENDED |
✓ | ✓ |
I metodi DragEvent
getAction()
, describeContents()
, writeToParcel()
e toString()
restituiscono sempre dati validi.
Se un metodo non contiene dati validi per un determinato tipo di azione, restituisce null
o 0, a seconda del tipo di risultato.
Ombra di trascinamento
Durante un'operazione di trascinamento, il sistema mostra un'immagine che l'utente trascina. Per lo spostamento dei dati, questa immagine rappresenta i dati trascinati. Per le altre operazioni, l'immagine rappresenta alcuni aspetti dell'operazione di trascinamento.
L'immagine è chiamata ombra di trascinamento. Puoi crearlo con i metodi dichiarati per un oggetto View.DragShadowBuilder
. Puoi passare il builder al sistema quando avvii un'operazione di trascinamento utilizzando startDragAndDrop()
. Come parte della sua risposta a startDragAndDrop()
, il sistema richiama i metodi di callback che hai definito in View.DragShadowBuilder
per ottenere un'ombra di trascinamento.
La classe View.DragShadowBuilder
ha due costruttori:
View.DragShadowBuilder(View)
Questo costruttore accetta tutti gli oggetti
View
dell'applicazione. Il costruttore archivia l'oggettoView
nell'oggettoView.DragShadowBuilder
, in modo che i callback possano accedervi per creare l'ombra di trascinamento. La vista non deve essere necessariamente un elementoView
selezionato dall'utente per avviare l'operazione di trascinamento.Se utilizzi questo costruttore, non devi estendere
View.DragShadowBuilder
o eseguire l'override dei relativi metodi. Per impostazione predefinita, ottieni un'ombra a trascinamento che ha lo stesso aspetto dell'oggettoView
trasmesso come argomento, centrata sotto la posizione in cui l'utente tocca lo schermo.View.DragShadowBuilder()
Se utilizzi questo costruttore, nell'oggetto
View.DragShadowBuilder
non sarà disponibile nessun oggettoView
. Il campo è impostato sunull
. Devi estendereView.DragShadowBuilder
e sostituire i relativi metodi, altrimenti otterrai un'ombra di trascinamento invisibile. Il sistema non genera un errore.
La classe View.DragShadowBuilder
ha due metodi che insieme creano l'ombra di trascinamento:
onProvideShadowMetrics()
Il sistema chiama questo metodo subito dopo la chiamata a
startDragAndDrop()
. Utilizza il metodo per inviare al sistema le dimensioni e il punto di contatto dell'ombra di trascinamento. Il metodo ha due parametri:outShadowSize
: un oggettoPoint
. La larghezza dell'ombra di trascinamento viene inserita inx
e l'altezza suy
.outShadowTouchPoint
: un oggettoPoint
. Il punto di contatto è la posizione all'interno dell'ombra del trascinamento che deve trovarsi sotto il dito dell'utente durante il trascinamento. La posizione X va inx
, mentre la posizione Y va iny
.onDrawShadow()
Subito dopo la chiamata a
onProvideShadowMetrics()
, il sistema chiamaonDrawShadow()
per creare l'ombra di trascinamento. Il metodo ha un singolo argomento, ovvero un oggettoCanvas
che il sistema crea dai parametri che fornisci inonProvideShadowMetrics()
. Il metodo disegna l'ombra di trascinamento sull'elementoCanvas
fornito.
Per migliorare le prestazioni, riduci le dimensioni dell'ombra del trascinamento. Per un singolo elemento, ti consigliamo di utilizzare un'icona. Per la selezione di più elementi, puoi utilizzare icone impilate anziché immagini intere disposte sullo schermo.
Un'operazione di trascinamento
Questa sezione mostra passo passo come avviare un trascinamento, rispondere agli eventi durante il trascinamento, rispondere a un evento di trascinamento e terminare l'operazione di trascinamento.
Avvia un trascinamento
L'utente avvia il trascinamento con un gesto di trascinamento, in genere toccando e tenendo premuto, su un oggetto View
. Di conseguenza, l'app deve:
Crea un oggetto
ClipData
e un oggettoClipData.Item
per i dati da spostare. Come parte diClipData
, fornisci i metadati archiviati in un oggettoClipDescription
all'interno diClipData
. Per un'operazione di trascinamento che non rappresenta lo spostamento dei dati, potresti utilizzarenull
anziché un oggetto effettivo.Ad esempio, questo snippet di codice mostra come rispondere a un gesto tocco e pressione su un
ImageView
creando un oggettoClipData
che contiene il tag (o l'etichetta) di unImageView
:Kotlin
// Create a string for the ImageView label. val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(context).apply { // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG setOnLongClickListener { v -> // Create a new ClipData. This is done in two steps to provide // clarity. The convenience method ClipData.newPlainText() can // create a plain text ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. val item = ClipData.Item(v.tag as? CharSequence) // Create a new ClipData using the tag as a label, the plain text // MIME type, and the already-created item. This creates a new // ClipDescription object within the ClipData and sets its MIME type // to "text/plain". val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiate the drag shadow builder. val myShadow = MyDragShadowBuilder(view: this) // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged. myShadow, // The drag shadow builder. null, // No need to use local data. 0 // Flags. Not currently used, set to 0. ) // Indicate that the long-click is handled. true } }
Java
// Create a string for the ImageView label. private static final String IMAGEVIEW_TAG = "icon bitmap"; ... // Create a new ImageView. ImageView imageView = new ImageView(context); // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. imageView.setImageBitmap(iconBitmap); // Set the tag. imageView.setTag(IMAGEVIEW_TAG); // Set a long-click listener for the ImageView using an anonymous listener // object that implements the OnLongClickListener interface. imageView.setOnLongClickListener( v -> { // Create a new ClipData. This is done in two steps to provide clarity. The // convenience method ClipData.newPlainText() can create a plain text // ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. ClipData.Item item = new ClipData.Item((CharSequence) v.getTag()); // Create a new ClipData using the tag as a label, the plain text MIME type, // and the already-created item. This creates a new ClipDescription object // within the ClipData and sets its MIME type to "text/plain". ClipData dragData = new ClipData( (CharSequence) v.getTag(), new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item); // Instantiate the drag shadow builder. View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView); // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged. myShadow, // The drag shadow builder. null, // No need to use local data. 0 // Flags. Not currently used, set to 0. ); // Indicate that the long-click is handled. return true; });
Definisci
myDragShadowBuilder
sostituendo i metodi inView.DragShadowBuilder
. Lo snippet di codice riportato di seguito crea una piccola ombra di trascinamento grigia e rettangolare per un elementoTextView
:Kotlin
private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) { private val shadow = ColorDrawable(Color.LTGRAY) // Define a callback that sends the drag shadow dimensions and touch point // back to the system. override fun onProvideShadowMetrics(size: Point, touch: Point) { // Set the width of the shadow to half the width of the original // View. val width: Int = view.width / 2 // Set the height of the shadow to half the height of the original // View. val height: Int = view.height / 2 // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height) // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height) // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2) } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas) } }
Java
private static class MyDragShadowBuilder extends View.DragShadowBuilder { // The drag shadow image, defined as a drawable object. private static Drawable shadow; // Constructor. public MyDragShadowBuilder(View view) { // Store the View parameter. super(view); // Create a draggable image that fills the Canvas provided by the // system. shadow = new ColorDrawable(Color.LTGRAY); } // Define a callback that sends the drag shadow dimensions and touch point // back to the system. @Override public void onProvideShadowMetrics (Point size, Point touch) { // Define local variables. int width, height; // Set the width of the shadow to half the width of the original // View. width = getView().getWidth() / 2; // Set the height of the shadow to half the height of the original // View. height = getView().getHeight() / 2; // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height); // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height); // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2); } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas); } }
Rispondere a un avvio del trascinamento
Durante l'operazione di trascinamento, il sistema invia gli eventi di trascinamento agli ascoltatori degli eventi di trascinamento degli oggetti View
nel layout corrente. I listener reagiscono chiamando DragEvent.getAction()
per ottenere il tipo di azione. All'inizio di un trascinamento, questo metodo restituisce ACTION_DRAG_STARTED
.
In risposta a un evento con il tipo di azione ACTION_DRAG_STARTED
, un listener di eventi di trascinamento deve:
Chiama
DragEvent.getClipDescription()
e utilizza i metodi di tipo MIME nel valoreClipDescription
restituito per vedere se il listener può accettare i dati trascinati.Se l'operazione di trascinamento non rappresenta lo spostamento dei dati, potrebbe non essere necessario.
Se il listener di eventi di trascinamento può accettare un rilascio, deve restituire
true
per comunicare al sistema di continuare a inviare eventi di trascinamento al listener. Se il listener non può accettare un rilascio, deve restituirefalse
e il sistema smette di inviare eventi di trascinamento al listener fino a quando non inviaACTION_DRAG_ENDED
per concludere l'operazione di trascinamento.
Per un evento ACTION_DRAG_STARTED
, i seguenti metodi DragEvent
non sono
validi: getClipData()
,
getX()
,
getY()
e
getResult()
.
Gestire gli eventi durante il trascinamento
Durante l'azione di trascinamento, i listener di eventi di trascinamento che restituiscono true
in risposta
all'evento di trascinamento ACTION_DRAG_STARTED
continuano a ricevere gli eventi di trascinamento. I tipi di eventi di trascinamento che un listener riceve durante il trascinamento dipendono dalla posizione dell'ombra di trascinamento e dalla visibilità dell'elemento View
del listener. I listener utilizzano gli eventi di trascinamento principalmente per decidere se modificare l'aspetto del proprio View
.
Durante l'azione di trascinamento, DragEvent.getAction()
restituisce uno dei tre valori seguenti:
ACTION_DRAG_ENTERED
: il listener riceve questo tipo di azione dell'evento quando il punto di contatto (il punto sullo schermo sotto il dito o il mouse dell'utente) entra nel riquadro di delimitazione delView
del listener.ACTION_DRAG_LOCATION
: una volta che il listener riceve un eventoACTION_DRAG_ENTERED
, riceve un nuovo eventoACTION_DRAG_LOCATION
ogni volta che il punto di contatto si sposta finché non riceve un eventoACTION_DRAG_EXITED
. I metodigetX()
egetY()
restituiscono le coordinate X e Y del punto di contatto.ACTION_DRAG_EXITED
: questo tipo di azione evento viene inviato a un listener che in precedenza riceveACTION_DRAG_ENTERED
. L'evento viene inviato quando il punto di contatto dell'ombra di trascinamento viene spostato dall'interno del riquadro di delimitazione delView
del listener all'esterno del riquadro di delimitazione.
Il listener di eventi di trascinamento non deve reagire a nessuno di questi tipi di azioni. Se il listener restituisce un valore al sistema, questo viene ignorato.
Di seguito sono riportate alcune linee guida per rispondere a ciascuno di questi tipi di azioni:
- In risposta a
ACTION_DRAG_ENTERED
oACTION_DRAG_LOCATION
, il listener può modificare l'aspetto diView
per indicare che la visualizzazione è un potenziale target di rilascio. - Un evento con il tipo di azione
ACTION_DRAG_LOCATION
contiene dati validi pergetX()
egetY()
corrispondenti alla posizione del punto di contatto. L'ascoltatore può utilizzare queste informazioni per modificare l'aspetto diView
nel punto di contatto o per determinare la posizione esatta in cui l'utente può rilasciare l'ombra di trascinamento, ovvero eliminare i dati. - In risposta a
ACTION_DRAG_EXITED
, il listener deve reimpostare qualsiasi modifica dell'aspetto applicata in risposta aACTION_DRAG_ENTERED
oACTION_DRAG_LOCATION
. Questo indica all'utente cheView
non è più un target di rilascio imminente.
Rispondere a un calo
Quando l'utente rilascia l'ombra di trascinamento su View
e in precedenza l'View
indica che può accettare i contenuti trascinati, il sistema invia un evento di trascinamento a View
con il tipo di azione ACTION_DROP
.
Il listener di eventi di trascinamento deve:
Chiama
getClipData()
per ottenere l'oggettoClipData
originariamente fornito nella chiamata astartDragAndDrop()
ed elaborare i dati. Se l'operazione di trascinamento non rappresenta lo spostamento dei dati, non è necessario.Restituisci il valore booleano
true
per indicare che il calo è stato elaborato correttamente, oppurefalse
in caso contrario. Il valore restituito diventa il valore restituito dagetResult()
per l'eventuale eventoACTION_DRAG_ENDED
. Se il sistema non invia un eventoACTION_DROP
, il valore restituito dagetResult()
per un eventoACTION_DRAG_ENDED
èfalse
.
Per un evento ACTION_DROP
, getX()
e getY()
usano il sistema di coordinate del View
che riceve il calo per restituire la posizione X e Y del punto di contatto al momento del lancio.
Il sistema consente all'utente di rilasciare l'ombra di trascinamento su un elemento View
il cui listener di eventi di trascinamento non riceve eventi di trascinamento. Consente inoltre all'utente di rilasciare l'ombreggiatura su regioni vuote dell'interfaccia utente dell'applicazione o su aree esterne all'applicazione. In tutti questi casi, il sistema non invia un evento con il tipo di azione ACTION_DROP
, anche se invia un evento ACTION_DRAG_ENDED
.
Rispondere a una fine del trascinamento
Subito dopo che l'utente rilascia l'ombra di trascinamento, il sistema invia un evento di trascinamento con un tipo di azione ACTION_DRAG_ENDED
a tutti i listener di eventi di trascinamento nell'applicazione. Questo indica che l'operazione di trascinamento è terminata.
Ogni listener di eventi di trascinamento deve:
- Se il listener modifica l'aspetto del relativo oggetto
View
durante l'operazione, deve reimpostare l'aspetto predefinito diView
. Si tratta di un'indicazione visiva per l'utente che l'operazione è terminata. - Il listener può facoltativamente chiamare
getResult()
per scoprire di più sull'operazione. Se un listener restituiscetrue
in risposta a un evento di tipo azioneACTION_DROP
,getResult()
restituisce l'operatore booleanotrue
. In tutti gli altri casi,getResult()
restituiscefalse
booleano, anche quando il sistema non invia un eventoACTION_DROP
. - Per indicare il completamento corretto dell'operazione di trascinamento, l'ascoltatore deve restituire
true
booleano al sistema.
Rispondere agli eventi di trascinamento: esempio
Tutti gli eventi di trascinamento vengono ricevuti dal metodo degli eventi di trascinamento o dal listener. Il seguente snippet di codice è un semplice esempio di risposta agli eventi di trascinamento:
Kotlin
val imageView = ImageView(this) // Set the drag event listener for the View. imageView.setOnDragListener { v, e -> // Handle each of the expected events. when (e.action) { DragEvent.ACTION_DRAG_STARTED -> { // Determine whether this View can accept the dragged data. if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true to indicate that the View can accept the dragged // data. true } else { // Return false to indicate that, during the current drag and // drop operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. false } } DragEvent.ACTION_DRAG_ENTERED -> { // Apply a green tint to the View. (v as? ImageView)?.setColorFilter(Color.GREEN) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DRAG_LOCATION -> // Ignore the event. true DragEvent.ACTION_DRAG_EXITED -> { // Reset the color tint to blue. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DROP -> { // Get the item containing the dragged data. val item: ClipData.Item = e.clipData.getItemAt(0) // Get the text data from the item. val dragData = item.text // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show() // Turn off color tints. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Return true. DragEvent.getResult() returns true. true } DragEvent.ACTION_DRAG_ENDED -> { // Turn off color tinting. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Do a getResult() and display what happens. when(e.result) { true -> Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG) else -> Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG) }.show() // Return true. The value is ignored. true } else -> { // An unknown action type is received. Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") false } } }
Java
View imageView = new ImageView(this); // Set the drag event listener for the View. imageView.setOnDragListener( (v, e) -> { // Handle each of the expected events. switch(e.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Determine whether this View can accept the dragged data. if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true to indicate that the View can accept the dragged // data. return true; } // Return false to indicate that, during the current drag and drop // operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. return false; case DragEvent.ACTION_DRAG_ENTERED: // Apply a green tint to the View. ((ImageView)v).setColorFilter(Color.GREEN); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event. return true; case DragEvent.ACTION_DRAG_EXITED: // Reset the color tint to blue. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DROP: // Get the item containing the dragged data. ClipData.Item item = e.getClipData().getItemAt(0); // Get the text data from the item. CharSequence dragData = item.getText(); // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show(); // Turn off color tints. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Return true. DragEvent.getResult() returns true. return true; case DragEvent.ACTION_DRAG_ENDED: // Turn off color tinting. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Do a getResult() and displays what happens. if (e.getResult()) { Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show(); } // Return true. The value is ignored. return true; // An unknown action type is received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
Trascina in modalità multi-finestra
I dispositivi con Android 7.0 (livello API 24) o versioni successive supportano la modalità multi-finestra, che consente agli utenti di spostare i dati da un'app all'altra tramite un'operazione di trascinamento. Per ulteriori informazioni, consulta Supporto multi-finestra.
L'app di origine, da cui inizia l'operazione di trascinamento, fornisce i dati. L'app di destinazione, dove termina l'operazione di trascinamento, riceve i dati.
All'avvio dell'operazione di trascinamento, l'app di origine deve impostare il flag DRAG_FLAG_GLOBAL
per indicare che l'utente può trascinare i dati in un'altra app.
Poiché i dati si spostano oltre i confini delle app, queste condividono l'accesso ai dati tramite un URI dei contenuti. Ciò richiede quanto segue:
- L'app di origine deve impostare uno o entrambi i flag
DRAG_FLAG_GLOBAL_URI_READ
eDRAG_FLAG_GLOBAL_URI_WRITE
, a seconda dell'accesso in lettura o scrittura ai dati che l'app di origine vuole concedere all'app di destinazione. - L'app di destinazione deve chiamare
requestDragAndDropPermissions()
immediatamente prima di gestire i dati che l'utente trascina nell'app. Se l'app di destinazione non ha più bisogno di accedere ai dati di trascinamento, l'app può chiamarerelease()
sull'oggetto restituito darequestDragAndDropPermissions()
. In caso contrario, le autorizzazioni vengono rilasciate quando l'attività che lo contiene viene eliminata. Se l'implementazione prevede l'avvio di una nuova attività per elaborare gli URI eliminati, dovrai concedere le stesse autorizzazioni alla nuova attività. Devi impostare i dati del clip e un flag:Kotlin
intent.setClipData(clipData) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
Java
intent.setClipData(clipData); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
I seguenti snippet di codice mostrano come rilasciare l'accesso di sola lettura per trascinare i dati subito dopo l'esecuzione dell'operazione di trascinamento. Per un esempio più completo, vedi l'esempio di DragonAndDrop su GitHub.
Attività di trascinamento dell'origine
Kotlin
// Drag a file stored in an images/ directory in internal storage. val internalImagesDir = File(context.filesDir, "images") val imageFile = File(internalImagesDir, imageFilename) val uri = FileProvider.getUriForFile(context, contentAuthority, imageFile) val listener = OnDragStartListener@{ view: View, _: DragStartHelper -> val clipData = ClipData(ClipDescription("Image Description", arrayOf("image/*")), ClipData.Item(uri)) // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps. // This example provides read-only access to the data. val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ return@OnDragStartListener view.startDragAndDrop(clipData, View.DragShadowBuilder(view), null, flags) } // Container where the image originally appears in the source app. val srcImageView = findViewById<ImageView>(R.id.imageView) // Detect and start the drag event. DragStartHelper(srcImageView, listener).apply { attach() }
Java
// Drag a file stored in an images/ directory in internal storage. File internalImagesDir = new File(context.getFilesDir(), "images"); File imageFile = new File(internalImagesDir, imageFilename); final Uri uri = FileProvider.getUriForFile(context, contentAuthority, imageFile); // Container where the image originally appears in the source app. ImageView srcImageView = findViewById(R.id.imageView); // Enable the view to detect and start the drag event. new DragStartHelper(srcImageView, (view, helper) -> { ClipData clipData = new ClipData(new ClipDescription("Image Description", new String[] {"image/*"}), new ClipData.Item(uri)); // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps. // This example provides read-only access to the data. int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ; return view.startDragAndDrop(clipData, new View.DragShadowBuilder(view), null, flags); }).attach();
Attività di trascinamento della selezione
Kotlin
// Container where the image is to be dropped in the target app. val targetImageView = findViewById<ImageView>(R.id.imageView) targetImageView.setOnDragListener { view, event -> when (event.action) { ACTION_DROP -> { val imageItem: ClipData.Item = event.clipData.getItemAt(0) val uri = imageItem.uri // Request permission to access the image data being dragged into // the target activity's ImageView element. val dropPermissions = requestDragAndDropPermissions(event) (view as ImageView).setImageURI(uri) // Release the permission immediately afterward because it's no // longer needed. dropPermissions.release() return@setOnDragListener true } // Implement logic for other DragEvent cases here. // An unknown action type is received. else -> { Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") return@setOnDragListener false } } }
Java
// Container where the image is to be dropped in the target app. ImageView targetImageView = findViewById(R.id.imageView); targetImageView.setOnDragListener( (view, event) -> { switch (event.getAction()) { case ACTION_DROP: ClipData.Item imageItem = event.getClipData().getItemAt(0); Uri uri = imageItem.getUri(); // Request permission to access the image data being dragged into // the target activity's ImageView element. DragAndDropPermissions dropPermissions = requestDragAndDropPermissions(event); ((ImageView)view).setImageURI(uri); // Release the permission immediately afterward because it's no // longer needed. dropPermissions.release(); return true; // Implement logic for other DragEvent cases here. // An unknown action type was received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
DropHelper per il trascinamento semplificato
La classe DropHelper
semplifica
l'implementazione delle funzionalità di trascinamento. Membro della libreria Jetpack
DragAndDrop
, DropHelper
fornisce la compatibilità con le versioni precedenti fino al livello API 24.
Usa DropHelper
per specificare i target di rilascio, personalizzare l'evidenziazione dei target di rilascio e definire in che modo vengono gestiti i dati eliminati.
Specifica i target di rilascio
DropHelper.configureView()
è un metodo statico e sovraccarico che ti consente di specificare i target di rilascio. I suoi parametri includono:
- L'attuale
Activity
, utilizzata per le autorizzazioni URI. - Le opzioni di configurazione per la destinazione di rilascio, in particolare un elenco di campi
EditText
incorporati. - Un
OnReceiveContentListener
per gestire i dati eliminati.
Ad esempio, per creare un target di rilascio che accetta immagini, utilizza una delle seguenti chiamate al metodo:
Kotlin
configureView( myActivity, targetView, arrayOf("image/*"), options, onReceiveContentListener) // or configureView( myActivity, targetView, arrayOf("image/*"), onReceiveContentListener)
Java
DropHelper.configureView( myActivity, targetView, new String[] {"image/*"}, options, onReceiveContentlistener); // or DropHelper.configureView( myActivity, targetView, new String[] {"image/*"}, onReceiveContentlistener);
Nella seconda chiamata vengono omesse le opzioni di configurazione della destinazione di rilascio. In questo caso il colore di evidenziazione della destinazione di rilascio è impostato sul colore secondario (o intenso) del tema, il raggio dell'angolo di evidenziazione è impostato su 16 dp e l'elenco di componenti EditText
è vuoto. Per informazioni dettagliate, consulta la sezione seguente.
Configura le destinazioni di rilascio
La classe interna DropHelper.Options
consente di configurare i target di rilascio. Fornisci un'istanza della classe al metodo DropHelper.configureView(Activity, View, String[], Options,
OnReceiveContentListener
. Per ulteriori informazioni, consulta la sezione precedente.
Personalizza l'evidenziazione del target di rilascio
DropHelper
configura i target dei rilasci in modo da visualizzare un'evidenziazione quando gli utenti trascinano i contenuti sui target. DropHelper
offre uno stile predefinito e DropHelper.Options
ti consente di impostare il colore dell'evidenziazione e specificare il raggio angolare del rettangolo dell'evidenziazione.
Utilizza la classe DropHelper.Options.Builder
per creare un'istanza DropHelper.Options
e impostare le opzioni di configurazione, come mostrato nell'esempio seguente:
Kotlin
val options: DropHelper.Options = DropHelper.Options.Builder() .setHighlightColor(getColor(R.color.purple_300)) .setHighlightCornerRadiusPx(resources.getDimensionPixelSize(R.dimen.drop_target_corner_radius)) .build()
Java
DropHelper.Options options = new DropHelper.Options.Builder() .setHighlightColor(getColor(R.color.purple_300)) .setHighlightCornerRadiusPx(getResources().getDimensionPixelSize(R.dimen.drop_target_corner_radius)) .build();
Gestire i componenti EditText nelle destinazioni degli elementi di rilascio
DropHelper
controlla anche lo stato attivo all'interno della destinazione del rilascio quando il target contiene campi di testo modificabili.
Gli obiettivi di rilascio possono essere una singola visualizzazione o una gerarchia di viste. Se la gerarchia delle viste della destinazione di rilascio contiene uno o più componenti EditText
, fornisci un elenco dei componenti per DropHelper.Options.Builder.addInnerEditTexts(EditText...)
per assicurarti che l'evidenziazione della destinazione di rilascio e la gestione dei dati di testo funzionino correttamente.
DropHelper
impedisce ai componenti EditText
all'interno della gerarchia di viste della destinazione di rilascio di sottrarre lo stato attivo alla vista contenitore durante le interazioni di trascinamento.
Inoltre, se il trascinamento ClipData
include dati di testo e URI, DropHelper
seleziona uno dei EditText
componenti nella destinazione del rilascio per gestire i dati di testo. La selezione si basa sul seguente ordine di precedenza:
- Il
EditText
in cui viene rilasciatoClipData
. - L'elemento
EditText
che contiene il cursore di testo (accento circonflesso). - Il primo
EditText
fornito alla chiamata aDropHelper.Options.Builder.addInnerEditTexts(EditText...)
.
Per impostare un EditText
come gestore dei dati di testo predefinito, passa EditText
come primo argomento della chiamata a DropHelper.Options.Builder.addInnerEditTexts(EditText...)
. Ad esempio, se la destinazione del lancio gestisce le immagini ma contiene i campi di testo modificabili T1
, T2
e T3
, imposta T2
come valore predefinito nel seguente modo:
Kotlin
val options: DropHelper.Options = DropHelper.Options.Builder() .addInnerEditTexts(T2, T1, T3) .build()
Java
DropHelper.Options options = new DropHelper.Options.Builder() .addInnerEditTexts(T2, T1, T3) .build();
Gestire i dati nelle destinazioni di rilascio
Il metodo DropHelper.configureView()
accetta un OnReceiveContentListener
creato da te per gestire il trascinamento ClipData
. I dati di trascinamento vengono forniti al listener in un oggetto ContentInfoCompat
.
Nell'oggetto sono presenti dati di testo. I contenuti multimediali, come le immagini, sono rappresentati da URI.
OnReceiveContentListener
gestisce anche i dati forniti alla destinazione di rilascio dalle interazioni degli utenti diverse dal trascinamento, ad esempio copia e incolla, quando DropHelper.configureView()
viene utilizzato per configurare i seguenti tipi di viste:
- Tutte le visualizzazioni, se l'utente utilizza Android 12 o versioni successive.
AppCompatEditText
, se l'utente ha una versione di Android con versioni precedenti ad Android 7.0.
Tipi MIME, autorizzazioni e convalida dei contenuti
Il controllo del tipo MIME effettuato da DropHelper
si basa sul trascinamento
ClipDescription
, creato dall'app che fornisce i dati di trascinamento. Convalida ClipDescription
per assicurarti che i tipi MIME siano impostati correttamente.
DropHelper
richiede tutte le autorizzazioni di accesso per gli URI dei contenuti contenuti nel trascinamento di ClipData
. Per maggiori informazioni, consulta
DragAndDropPermissions
. Le autorizzazioni consentono di risolvere gli URI dei contenuti durante l'elaborazione dei dati di trascinamento.
DropHelper
non convalida i dati restituiti dai fornitori di contenuti durante la risoluzione degli URI nei dati eliminati. Controlla se è presente un valore nullo e verifica la correttezza
dei dati risolti.