Saltar al contenido principal

Arrastrar un elemento de la UI

Cómo implementar un elemento de la UI arrastrable.

Arrastrar y soltar (drag and drop) es una interacción común en apps móviles. A medida que el usuario realiza una pulsación larga (a veces llamada touch & hold) en un widget, aparece otro widget debajo del dedo del usuario, y el usuario arrastra el widget a una ubicación final y lo suelta. En esta receta, construirás una interacción de arrastrar y soltar donde el usuario realiza una pulsación larga sobre una opción de comida, y luego arrastra esa comida a la foto del cliente que la está pagando.

La siguiente animación muestra el comportamiento de la app:

Ordenar la comida arrastrándola hacia la persona

Esta receta comienza con una lista preconstruida de elementos del menú y una fila de clientes. El primer paso es reconocer una pulsación larga y mostrar una foto arrastrable de un elemento del menú.

Presionar y arrastrar

#

Flutter proporciona un widget llamado LongPressDraggable que proporciona exactamente el comportamiento que necesitas para comenzar una interacción de arrastrar y soltar. Un widget LongPressDraggable reconoce cuándo ocurre una pulsación larga y luego muestra un nuevo widget cerca del dedo del usuario. A medida que el usuario arrastra, el widget sigue el dedo del usuario. LongPressDraggable te da control total sobre el widget que el usuario arrastra.

Cada elemento de la lista de menú se muestra con un widget personalizado MenuListItem.

dart
MenuListItem(
  name: item.name,
  price: item.formattedTotalItemPrice,
  photoProvider: item.imageProvider,
)

Envuelve el widget MenuListItem con un widget LongPressDraggable.

dart
LongPressDraggable<Item>(
  data: item,
  dragAnchorStrategy: pointerDragAnchorStrategy,
  feedback: DraggingListItem(
    dragKey: _draggableKey,
    photoProvider: item.imageProvider,
  ),
  child: MenuListItem(
    name: item.name,
    price: item.formattedTotalItemPrice,
    photoProvider: item.imageProvider,
  ),
);

En este caso, cuando el usuario realiza una pulsación larga en el widget MenuListItem, el widget LongPressDraggable muestra un DraggingListItem. Este DraggingListItem muestra una foto del elemento de comida seleccionado, centrado debajo del dedo del usuario.

La propiedad dragAnchorStrategy se establece en pointerDragAnchorStrategy. El valor de esta propiedad le indica a LongPressDraggable que base la posición de DraggableListItem en el dedo del usuario. A medida que el usuario mueve un dedo, el DraggableListItem se mueve con él.

Arrastrar y soltar es de poca utilidad si no se transmite información cuando se suelta el elemento. Por esta razón, LongPressDraggable toma un parámetro data. En este caso, el tipo de data es Item, que contiene información sobre el elemento de menú de comida sobre el que presionó el usuario.

Los datos (data) asociados con un LongPressDraggable se envían a un widget especial llamado DragTarget, donde el usuario suelta el gesto de arrastre. Implementarás el comportamiento de soltar a continuación.

Soltar el elemento arrastrable

#

El usuario puede soltar un LongPressDraggable donde prefiera, pero soltarlo no tiene ningún efecto a menos que se suelte encima de un DragTarget. Cuando el usuario suelta un elemento arrastrable encima de un widget DragTarget, el widget DragTarget puede aceptar o rechazar los datos del elemento arrastrable.

En esta receta, el usuario debe soltar un elemento del menú en un widget CustomerCart para añadir el elemento del menú al carrito del usuario.

dart
CustomerCart(
  hasItems: customer.items.isNotEmpty,
  highlighted: candidateItems.isNotEmpty,
  customer: customer,
);

Envuelve el widget CustomerCart con un widget DragTarget.

dart
DragTarget<Item>(
  builder: (context, candidateItems, rejectedItems) {
    return CustomerCart(
      hasItems: customer.items.isNotEmpty,
      highlighted: candidateItems.isNotEmpty,
      customer: customer,
    );
  },
  onAcceptWithDetails: (details) {
    _itemDroppedOnCustomerCart(item: details.data, customer: customer);
  },
)

El DragTarget muestra tu widget existente y también se coordina con LongPressDraggable para reconocer cuándo el usuario arrastra un elemento arrastrable encima del DragTarget. El DragTarget también reconoce cuándo el usuario suelta un elemento arrastrable encima del widget DragTarget.

Cuando el usuario arrastra un elemento arrastrable sobre el widget DragTarget, candidateItems contiene los elementos de datos que el usuario está arrastrando. Esto te permite cambiar la apariencia de tu widget cuando el usuario está arrastrando sobre él. En este caso, el widget Customer se vuelve rojo cuando se arrastran elementos por encima del widget DragTarget. La apariencia visual roja se configura con la propiedad highlighted dentro del widget CustomerCart.

Cuando el usuario suelta un elemento arrastrable sobre el widget DragTarget, se invoca el callback onAcceptWithDetails. Es en este momento cuando puedes decidir si aceptas o no los datos que se soltaron. En este caso, el elemento siempre se acepta y se procesa. Podrías elegir inspeccionar el elemento entrante para tomar una decisión diferente.

Ten en cuenta que el tipo de elemento que se suelta en DragTarget debe coincidir con el tipo del elemento arrastrado desde LongPressDraggable. Si los tipos no son compatibles, entonces el método onAcceptWithDetails no se invoca.

Con un widget DragTarget configurado para aceptar los datos deseados, ahora puedes transmitir datos de una parte de tu UI a otra arrastrando y soltando.

En el siguiente paso, actualizarás el carrito del cliente con el elemento de menú que se soltó.

Añadir un elemento del menú a un carrito

#

Cada cliente está representado por un objeto Customer, que mantiene un carrito de elementos y un precio total.

dart
class Customer {
  Customer({required this.name, required this.imageProvider, List<Item>? items})
    : items = items ?? [];

  final String name;
  final ImageProvider imageProvider;
  final List<Item> items;

  String get formattedTotalItemPrice {
    final totalPriceCents = items.fold<int>(
      0,
      (prev, item) => prev + item.totalPriceCents,
    );
    return '\$${(totalPriceCents / 100.0).toStringAsFixed(2)}';
  }
}

El widget CustomerCart muestra la foto del cliente, el nombre, el total y la cantidad de elementos basándose en una instancia de Customer.

Para actualizar el carrito de un cliente cuando se suelta un elemento de menú, añade el elemento soltado al objeto Customer asociado.

dart
void _itemDroppedOnCustomerCart({
  required Item item,
  required Customer customer,
}) {
  setState(() {
    customer.items.add(item);
  });
}

El método _itemDroppedOnCustomerCart se invoca en onAcceptWithDetails() cuando el usuario suelta un elemento de menú en un widget CustomerCart. Al añadir el elemento soltado al objeto customer, e invocar setState() para provocar una actualización del diseño, la UI se refresca con el total de precio y cantidad de elementos del nuevo cliente.

¡Felicitaciones! Tienes una interacción de arrastrar y soltar que añade elementos de comida al carrito de compras de un cliente.

Ejemplo interactivo

#

Ejecutar la app:

  • Desplazarse por los elementos de comida.
  • Mantén presionado uno con tu dedo o haz clic y mantén presionado con el ratón.
  • Mientras lo sostienes, la imagen del elemento de comida aparecerá encima de la lista.
  • Arrastra la imagen y suéltala sobre una de las personas en la parte inferior de la pantalla. El texto debajo de la imagen se actualiza para reflejar el cobro para esa persona. Puedes continuar agregando elementos de comida y ver cómo se acumulan los cobros.