Vous pouvez implémenter votre processus de glisser-déposer dans les vues en répondant aux événements qui peut déclencher un glissement-début, répondre et consommer des événements de dépôt.
Lancer un déplacement
L'utilisateur lance un glissement avec un geste, généralement en touchant ou en cliquant et en maintenant l'élément qu'il souhaite faire glisser.
Pour gérer cela dans un View
, créez un
l'objet ClipData
et
Objet ClipData.Item
pour
les données en cours de transfert. Dans ClipData
, fournissez les métadonnées qui sont
stocké dans un
Objet ClipDescription
dans les ClipData
. Pour une opération de glisser-déposer qui ne représente pas
transfert de données, vous pouvez utiliser null
au lieu d'un objet réel.
Par exemple, cet extrait de code montre comment répondre à une commande tactile appui long
sur un ImageView
en créant un objet ClipData
contenant la
tag (ou libellé) d'un ImageView
:
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. We use this imageView object // to create the default builder. val myShadow = View.DragShadowBuilder(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. We use this imageView object // to create the default builder. View.DragShadowBuilder myShadow = new View.DragShadowBuilder(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; });
Répondre à un début de déplacement
Pendant l'opération de déplacement, le système envoie des événements de déplacement à l'événement de déplacement
écouteurs des objets View
dans la mise en page actuelle. Les écouteurs réagissent
en appelant DragEvent.getAction()
pour obtenir le type d'action. Au début d'un glissement,
cette méthode renvoie ACTION_DRAG_STARTED
.
En réponse à un événement de type d'action ACTION_DRAG_STARTED
, un événement de déplacement
L'écouteur doit procéder comme suit:
Appeler
DragEvent.getClipDescription()
et utiliser les méthodes de type MIME dans leClipDescription
renvoyé pour voir si l'écouteur peut accepter les données déplacées.Si l'opération de glisser-déposer ne représente pas le déplacement des données, cela peut être inutile.
Si l'écouteur d'événements de déplacement peut accepter un dépôt, il doit renvoyer
true
pour indiquer au système de continuer à envoyer des événements de déplacement à l'écouteur. Si l'écouteur n'accepte pas de suppression, l'écouteur doit renvoyerfalse
, et le système s'arrête en envoyant des événements de déplacement à l'écouteur jusqu'à ce que le système envoieACTION_DRAG_ENDED
pour terminer l'opération de glisser-déposer.
Pour un événement ACTION_DRAG_STARTED
, les méthodes DragEvent
suivantes ne sont pas
valide: getClipData()
,
getX()
,
getY()
et
getResult()
Gérer les événements pendant le déplacement
Pendant l'action de déplacement, faites glisser les écouteurs d'événements qui renvoient true
en réponse à
l'événement de déplacement ACTION_DRAG_STARTED
continue à les recevoir. Types
d'événements de déplacement qu'un écouteur reçoit pendant le déplacement dépendent de l'emplacement
une ombre de glissement et la visibilité de l'View
de l'écouteur. Les écouteurs utilisent la commande
pour décider s'ils doivent modifier l'apparence de leur View
.
Lors de l'action de déplacement, DragEvent.getAction()
renvoie l'une des trois valeurs suivantes:
ACTION_DRAG_ENTERED
: l'écouteur reçoit ce type d'action d'événement lorsque le point de contact (le point sur l'écran sous le doigt ou la souris de l'utilisateur — entre cadre de délimitation de l'élémentView
de l'écouteur.ACTION_DRAG_LOCATION
: Une fois que l'écouteur reçoit un événementACTION_DRAG_ENTERED
, il reçoit une nouvelleACTION_DRAG_LOCATION
chaque fois que le point de contact se déplace jusqu'à ce qu'il reçoit un événementACTION_DRAG_EXITED
. Les méthodesgetX()
etgetY()
renvoient les coordonnées X et Y du point de contact.ACTION_DRAG_EXITED
: ce type d'action d'événement est envoyé à un écouteur qui reçoit précédemmentACTION_DRAG_ENTERED
L'événement est envoyé lorsque le point de contact avec l'ombre de glissement se déplace de l'intérieur du cadre de délimitation duView
de l'écouteur vers l'extérieur de cadre de délimitation.
L'écouteur d'événements de déplacement n'a pas besoin de réagir à ces types d'actions. Si l'écouteur renvoie une valeur au système, elle est ignorée.
Vous trouverez ci-dessous quelques consignes pour répondre à chacun de ces types d'actions:
- En réponse à
ACTION_DRAG_ENTERED
ouACTION_DRAG_LOCATION
, l'écouteur Vous pouvez modifier l'apparence deView
pour indiquer que la vue est une cible de chute potentielle. - Un événement avec le type d'action
ACTION_DRAG_LOCATION
contient des données valides pourgetX()
etgetY()
correspondant à l'emplacement du point de contact. La l'écouteur peut utiliser ces informations pour modifier l'apparence deView
au niveau point de contact ou pour déterminer la position exacte à laquelle l'utilisateur peut déposer le contenus. - En réponse à
ACTION_DRAG_EXITED
, l'écouteur doit réinitialiser toute apparence. les modifications qu'elle applique en réponse àACTION_DRAG_ENTERED
ouACTION_DRAG_LOCATION
Cela indique à l'utilisateur queView
n'est pas une cible de dépôt imminente.
Répondre à une chute
Lorsque l'utilisateur libère l'ombre de glissement sur un View
, et le View
précédemment
signale qu'il peut accepter le contenu déplacé, le système envoie une
Faites glisser l'événement vers View
avec le type d'action ACTION_DROP
.
L'écouteur d'événements de déplacement doit effectuer les opérations suivantes:
Appelez
getClipData()
pour obtenir l'objetClipData
initialement fournies dans l'appelstartDragAndDrop()
et traiter les données. Si l'opération de glisser-déposer ne représente pas de données mouvement, c'est inutile.Renvoyez la valeur booléenne
true
pour indiquer que la suppression a bien été traitée. oufalse
dans le cas contraire. La valeur renvoyée devient la valeur renvoyée pargetResult()
pour l'événementACTION_DRAG_ENDED
éventuel. Si le système n'envoie pas d'événementACTION_DROP
, la valeur renvoyée pargetResult()
pour un événementACTION_DRAG_ENDED
estfalse
.
Pour un événement ACTION_DROP
, getX()
et getY()
utilisent le système de coordonnées de
View
qui reçoit la baisse pour renvoyer les positions X et Y de la
point de contact au moment de la chute.
Alors que l'utilisateur est en mesure de libérer l'ombre de glissement sur un View
dont l'événement de déplacement
l'écouteur ne reçoit pas d'événements de déplacement, de régions vides de l'UI de votre appli ou même
sur des zones en dehors de votre application, Android n'envoie pas d'événement avec action
saisissez ACTION_DROP
et n'enverra qu'un événement ACTION_DRAG_ENDED
.
Répondre à une fin de glissement
Immédiatement après que l'utilisateur libère l'ombre de déplacement, le système envoie un déplacement
avec un type d'action ACTION_DRAG_ENDED
à tous les écouteurs d'événements de déplacement
dans votre application. Cela indique que l'opération de déplacement est terminée.
Chaque écouteur d'événements de déplacement doit:
- Si l'écouteur change d'apparence pendant l'opération, il doit être réinitialisé. à son apparence par défaut comme une indication visuelle pour l'utilisateur que la est terminée.
- L'écouteur peut éventuellement appeler
getResult()
pour en savoir plus sur la opération. Si un écouteur renvoietrue
en réponse à un événement d'action saisissezACTION_DROP
, puisgetResult()
renvoie la valeur booléennetrue
. Dans toutes les autres cas,getResult()
renvoie la valeur booléennefalse
, y compris lorsque le système n'envoie pas d'événementACTION_DROP
. - Pour indiquer que l'opération de suppression a abouti, l'écouteur
doit renvoyer la valeur booléenne
true
au système. Si vous ne renvoyez pasfalse
, une un repère visuel montrant que l'ombre projetée revient à sa source peut suggérer que l'opération a échoué.
Répondre à des événements de déplacement: exemple
Tous les événements de déplacement sont reçus par votre méthode d'événement de déplacement ou votre écouteur. La L'extrait de code suivant est un exemple de réponse aux événements de déplacement:
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; });
Personnaliser une ombre de déplacement
Vous pouvez définir un myDragShadowBuilder
personnalisé en remplaçant les méthodes dans
View.DragShadowBuilder
L'extrait de code suivant crée un petit fichier
ombre de glissement grise rectangulaire pour TextView
:
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); } }