Skip to content

implicit a11y scrolling broken with pinned & floating AppBar #35488

@goderbauer

Description

@goderbauer
  1. Run the following code:
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);


  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            title: Text('Hello'),
            pinned: true,
            floating: true,
            expandedHeight: 200.0,
          ),
          SliverFixedExtentList(
            itemExtent: 100.0,
            delegate: SliverChildBuilderDelegate(
              (BuildContext c, int index) {
                return Container(
                  height: 100.0,
                  color: index % 2 == 0 ? Colors.red : Colors.yellow,
                  child: Text('Tile $index'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
  1. Scroll down a bit
  2. turn on talkback
  3. focus on an item in the list
  4. swipe left a couple of times

Expected behavior:
When the a11y focus reaches an item that's partially visible, it should be brought fully on screen. When moving a11y focus to the next off-screen item, the list should scroll and a11y focus should move to the next item

Observed behavior:
Partially visible items are not brought on screen. When moving focus to an offscreen item it moves to the AppBar and the list scrolls all the way to the top.

It looks like the semantics tree is already incorrect:

I/flutter ( 8037): SemanticsNode#0
I/flutter ( 8037):  │ Rect.fromLTRB(0.0, 0.0, 1440.0, 2392.0)
I/flutter ( 8037):  │
I/flutter ( 8037):  └─SemanticsNode#1
I/flutter ( 8037):    │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4) scaled by 3.5x
I/flutter ( 8037):    │ textDirection: ltr
I/flutter ( 8037):    │
I/flutter ( 8037):    └─SemanticsNode#2
I/flutter ( 8037):      │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)
I/flutter ( 8037):      │ flags: scopesRoute
I/flutter ( 8037):      │
I/flutter ( 8037):      ├─SemanticsNode#3
I/flutter ( 8037):      │ │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)
I/flutter ( 8037):      │ │
I/flutter ( 8037):      │ └─SemanticsNode#14
I/flutter ( 8037):      │   │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)
I/flutter ( 8037):      │   │ actions: scrollDown, scrollUp
I/flutter ( 8037):      │   │ flags: hasImplicitScrolling
I/flutter ( 8037):      │   │ scrollIndex: 23
I/flutter ( 8037):      │   │ scrollExtentMin: 0.0
I/flutter ( 8037):      │   │ scrollPosition: 2500.0
I/flutter ( 8037):      │   │ scrollExtentMax: Infinity
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#137
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, -250.0, 411.4, -176.0)
I/flutter ( 8037):      │   │   flags: isHidden
I/flutter ( 8037):      │   │   HIDDEN
I/flutter ( 8037):      │   │   label: "Tile 20"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#138
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, -176.0, 411.4, -76.0)
I/flutter ( 8037):      │   │   flags: isHidden
I/flutter ( 8037):      │   │   HIDDEN
I/flutter ( 8037):      │   │   label: "Tile 21"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#139
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, -76.0, 411.4, 24.0)
I/flutter ( 8037):      │   │   flags: isHidden
I/flutter ( 8037):      │   │   HIDDEN
I/flutter ( 8037):      │   │   label: "Tile 22"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#12
I/flutter ( 8037):      │   │ │ Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)
I/flutter ( 8037):      │   │ │
I/flutter ( 8037):      │   │ └─SemanticsNode#13
I/flutter ( 8037):      │   │     Rect.fromLTRB(16.0, 40.5, 63.0, 63.5)
I/flutter ( 8037):      │   │     flags: isHeader, namesRoute
I/flutter ( 8037):      │   │     label: "Hello"
I/flutter ( 8037):      │   │     textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#140
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 80.0, 411.4, 124.0)
I/flutter ( 8037):      │   │   label: "Tile 23"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#141
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 124.0, 411.4, 224.0)
I/flutter ( 8037):      │   │   label: "Tile 24"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#142
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 224.0, 411.4, 324.0)
I/flutter ( 8037):      │   │   label: "Tile 25"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#143
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 324.0, 411.4, 424.0)
I/flutter ( 8037):      │   │   label: "Tile 26"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#144
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 424.0, 411.4, 524.0)
I/flutter ( 8037):      │   │   label: "Tile 27"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#145
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 524.0, 411.4, 624.0)
I/flutter ( 8037):      │   │   label: "Tile 28"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#146
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 624.0, 411.4, 683.4)
I/flutter ( 8037):      │   │   label: "Tile 29"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#147
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 724.0, 411.4, 824.0)
I/flutter ( 8037):      │   │   flags: isHidden
I/flutter ( 8037):      │   │   HIDDEN
I/flutter ( 8037):      │   │   label: "Tile 30"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   ├─SemanticsNode#148
I/flutter ( 8037):      │   │   Rect.fromLTRB(0.0, 824.0, 411.4, 924.0)
I/flutter ( 8037):      │   │   flags: isHidden
I/flutter ( 8037):      │   │   HIDDEN
I/flutter ( 8037):      │   │   label: "Tile 31"
I/flutter ( 8037):      │   │   textDirection: ltr
I/flutter ( 8037):      │   │
I/flutter ( 8037):      │   └─SemanticsNode#149
I/flutter ( 8037):      │       Rect.fromLTRB(0.0, 924.0, 411.4, 933.4)
I/flutter ( 8037):      │       flags: isHidden
I/flutter ( 8037):      │       HIDDEN
I/flutter ( 8037):      │       label: "Tile 32"
I/flutter ( 8037):      │       textDirection: ltr
I/flutter ( 8037):      │
I/flutter ( 8037):      └─SemanticsNode#15
I/flutter ( 8037):        │ merge boundary ⛔️
I/flutter ( 8037):        │ Rect.fromLTRB(0.0, 0.0, 56.0, 56.0) with transform
I/flutter ( 8037):        │ [1.0,2.4492935982947064e-16,0.0,339.42857142857144;
I/flutter ( 8037):        │ -2.4492935982947064e-16,1.0,0.0,611.4285714285714;
I/flutter ( 8037):        │ 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0]
I/flutter ( 8037):        │ label: "Increment"
I/flutter ( 8037):        │ textDirection: ltr
I/flutter ( 8037):        │
I/flutter ( 8037):        └─SemanticsNode#16
I/flutter ( 8037):            merged up ⬆️
I/flutter ( 8037):            Rect.fromLTRB(0.0, 0.0, 56.0, 56.0)
I/flutter ( 8037):            actions: tap
I/flutter ( 8037):            flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8037):            thickness: 6.0
I/flutter ( 8037): 

The app bar nod (label: "Hallo") shouldn't be in the middle of all the tiles within the scrollable node.

This reproduces on Android, still investigating if iOS is also affected.

Metadata

Metadata

Assignees

Labels

a: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)f: scrollingViewports, list views, slivers, etc.frameworkflutter/packages/flutter repository. See also f: labels.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions