Saltar al contenido principal

PrimaryScrollController predeterminado en Desktop

The `PrimaryScrollController` will no longer attach to vertical `ScrollView`s automatically on Desktop.

Resumen

#

La API de PrimaryScrollController se ha actualizado para que ya no se asocie automáticamente a los ScrollViews verticales en plataformas de escritorio (desktop).

Contexto

#

Antes de este cambio, ScrollView.primary tenía como valor predeterminado true si un ScrollView tenía una dirección de desplazamiento Axis.vertical y no se había proporcionado ya un ScrollController. Esto permitía que patrones de UI comunes, como la función de deslizar hacia arriba en iOS, funcionaran de inmediato para las aplicaciones de Flutter. Sin embargo, en escritorio, este valor predeterminado a menudo causaba el siguiente error de aserción:

ScrollController attached to multiple ScrollViews.

Aunque es común que una aplicación móvil muestre un ScrollView a la vez, los patrones de UI de escritorio son más propensos a mostrar múltiples ScrollViews uno al lado del otro. La implementación anterior de PrimaryScrollController entraba en conflicto con este patrón, lo que resultaba en un mensaje de error que a menudo no era útil. Para solucionar esto, PrimaryScrollController se ha actualizado con parámetros adicionales, así como mejores mensajes de error en múltiples widgets que dependen de él.

Descripción del cambio

#

La implementación anterior de ScrollView hacía que primary fuera true por defecto para todos los ScrollViews verticales que no tuvieran ya un ScrollController, en todas las plataformas. Este comportamiento predeterminado no siempre era claro, particularmente porque es independiente del propio PrimaryScrollController.

dart
// Previously, this ListView would always result in primary being true,
// and attached to the PrimaryScrollController on all platforms.
Scaffold(
  body: ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Text('Item $index');
    }
  ),
);

La implementación cambia ScrollView.primary para que admita valores nulos (nullable), reubicando la toma de decisiones de respaldo en PrimaryScrollController. Cuando primary es null y no se ha proporcionado ningún ScrollController, el ScrollView buscará el PrimaryScrollController y en su lugar llamará a shouldInherit para determinar si el ScrollView dado debe usar el PrimaryScrollController.

Los nuevos miembros de la clase PrimaryScrollController, automaticallyInheritForPlatforms y scrollDirection, se evalúan en shouldInherit, lo que brinda a los usuarios claridad y control sobre el comportamiento de PrimaryScrollController.

Por defecto, se mantiene la compatibilidad con versiones anteriores para plataformas móviles. PrimaryScrollController.shouldInherit devuelve true para ScrollViews verticales. En escritorio, este devuelve false de forma predeterminada.

dart
// Only on mobile platforms will this attach to the PrimaryScrollController by
// default.
Scaffold(
  body: ListView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Text('Item $index');
    }
  ),
);

Para cambiar el comportamiento predeterminado, los usuarios pueden establecer ScrollView.primary en true o false para gestionar explícitamente el PrimaryScrollController para un ScrollView individual. Para el comportamiento en múltiples ScrollViews, el PrimaryScrollController ahora es configurable configurando la plataforma específica, así como la dirección de desplazamiento que se prefiere para la herencia.

Los Widgets que usan el PrimaryScrollController, como NestedScrollView, Scrollbar y DropdownMenuButton no experimentarán ningún cambio en su funcionalidad existente. Características como el scroll-to-top de iOS también continuarán funcionando como se espera sin ninguna migración.

Las clases ScrollAction y ScrollIntent en escritorio son las únicas afectadas por este cambio y requieren migración. De forma predeterminada, PrimaryScrollController se utiliza para ejecutar el desplazamiento de teclado de respaldo (Shortcuts) si el Focus actual está contenido dentro de un Scrollable. Dado que mostrar más de un ScrollView uno al lado del otro es común en plataformas de escritorio, no es posible para Flutter decidir "¿Qué ScrollView debería ser el primario en esta vista y recibir la acción de desplazamiento del teclado?"

Si había más de un ScrollView presente antes de este cambio, se lanzaba la misma aserción (ScrollController attached to multiple ScrollViews.). Ahora, en plataformas de escritorio, los usuarios deben especificar primary: true para designar qué ScrollView es el respaldo para recibir Shortcuts de teclado no controlados.

Guía de migración

#

Código antes de la migración:

dart
// These side-by-side ListViews would throw errors from Scrollbars and
// ScrollActions previously due to the PrimaryScrollController.
Scaffold(
  body: LayoutBuilder(
    builder: (context, constraints) {
      return Row(
        children: [
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return Text('List 1 - Item $index');
              }
            ),
          ),
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return Text('List 2 - Item $index');
              }
            ),
          ),
        ]
      );
    },
  ),
);

Código después de la migración:

dart
// These side-by-side ListViews will no longer throw errors, but for
// default ScrollActions, one will need to be designated as primary.
Scaffold(
  body: LayoutBuilder(
    builder: (context, constraints) {
      return Row(
        children: [
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              // This ScrollView will use the PrimaryScrollController
              primary: true,
              itemBuilder: (BuildContext context, int index) {
                return Text('List 1 - Item $index');
              }
            ),
          ),
          SizedBox(
            height: constraints.maxHeight,
            width: constraints.maxWidth / 2,
            child: ListView.builder(
              itemBuilder: (BuildContext context, int index) {
                return Text('List 2 - Item $index');
              }
            ),
          ),
        ]
      );
    },
  ),
);

Timeline

#

Llegó en la versión: 3.3.0-0.0.pre
En lanzamiento estable: 3.3

Referencias

#

Documentación de la API:

Documento de diseño:

Issues relevantes:

PRs relevantes: