Saltar al contenido principal

Orden de semántica de las entradas de Overlay en rutas modales

El scope de la ruta modal tiene un orden de recorrido semántico superior al de su barrera modal.

Resumen

#

Cambiamos el orden de recorrido semántico de las entradas de superposición (overlay entries) en las rutas modales. Las funciones de accesibilidad TalkBack o VoiceOver ahora enfocan primero el scope de una ruta modal en lugar de su barrera modal.

Contexto

#

La ruta modal tiene dos entradas de superposición: el scope y la barrera modal. El scope es el contenido real de la ruta modal, y la barrera modal es el fondo de la ruta si su scope no cubre toda la pantalla. Si la ruta modal devuelve true para barrierDismissible, la barrera modal pasa a ser enfocable por accesibilidad debido a que los usuarios pueden tocar la barrera modal para cerrar la ruta modal. Este cambio hizo específicamente que la accesibilidad enfoque primero el scope antes que la barrera modal.

Descripción del cambio

#

Añadimos un nodo semántico adicional por encima de ambas entradas de superposición de las rutas modales. Esos nodos semánticos denotan el orden de recorrido semántico de estas dos entradas de superposición. Esto también cambió la estructura del árbol semántico.

Guía de migración

#

Si tus pruebas comienzan a fallar debido a cambios en el árbol semántico después de la actualización, puedes migrar tu código esperando un nuevo nodo por encima de las entradas de superposición de la ruta modal.

Código antes de la migración:

dart
import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';

void main() {
  testWidgets('example test', (WidgetTester tester) async {
    final SemanticsHandle handle =
        tester.binding.pipelineOwner.ensureSemantics();

    // Build our app and trigger a frame.
    await tester.pumpWidget(MaterialApp(home: Scaffold(body: Text('test'))));

    final SemanticsNode root =
        tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;

    final SemanticsNode firstNode = getChild(root);
    expect(firstNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));

    // Fixes the test by expecting an additional node above the scope route.
    final SemanticsNode secondNode = getChild(firstNode);
    expect(secondNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));

    final SemanticsNode thirdNode = getChild(secondNode);
    expect(thirdNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
    expect(thirdNode.hasFlag(SemanticsFlag.scopesRoute), true);

    final SemanticsNode forthNode = getChild(thirdNode);
    expect(forthNode.rect, Rect.fromLTRB(0.0, 0.0, 56.0, 14.0));
    expect(forthNode.label, 'test');
    handle.dispose();
  });
}

SemanticsNode getChild(SemanticsNode node) {
  SemanticsNode child;
  bool visitor(SemanticsNode target) {
    child = target;
    return false;
  }

  node.visitChildren(visitor);
  return child;
}

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

dart
import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';

void main() {
  testWidgets('example test', (WidgetTester tester) async {
    final SemanticsHandle handle =
        tester.binding.pipelineOwner.ensureSemantics();

    // Build our app and trigger a frame.
    await tester.pumpWidget(MaterialApp(home: Scaffold(body: Text('test'))));

    final SemanticsNode root =
        tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;

    final SemanticsNode firstNode = getChild(root);
    expect(firstNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));

    // Fixes the test by expecting an additional node above the scope route.
    final SemanticsNode secondNode = getChild(firstNode);
    expect(secondNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));

    final SemanticsNode thirdNode = getChild(secondNode);
    expect(thirdNode.rect, Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
    expect(thirdNode.hasFlag(SemanticsFlag.scopesRoute), true);

    final SemanticsNode forthNode = getChild(thirdNode);
    expect(forthNode.rect, Rect.fromLTRB(0.0, 0.0, 56.0, 14.0));
    expect(forthNode.label, 'test');
    handle.dispose();
  });
}

SemanticsNode getChild(SemanticsNode node) {
  SemanticsNode child;
  bool visitor(SemanticsNode target) {
    child = target;
    return false;
  }

  node.visitChildren(visitor);
  return child;
}

Timeline

#

Introducido en la versión: 1.19.0
En la versión estable: 1.20

Referencias

#

Documentación de la API:

Problema relevante:

PR relevante: