custom_semantics.dart 4.33 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

/// A [ListTile] containing a dropdown menu that exposes itself as an
/// "Adjustable" to screen readers (e.g. TalkBack on Android and VoiceOver on
/// iOS).
///
/// This allows screen reader users to swipe up/down (on iOS) or use the volume
/// keys (on Android) to switch between the values in the dropdown menu.
/// Depending on what the values in the dropdown menu are this can be a more
/// intuitive way of switching values compared to exposing the content of the
/// drop down menu as a screen overlay from which the user can select.
///
/// Users that do not use a screen reader will just see a regular dropdown menu.
class AdjustableDropdownListTile extends StatelessWidget {
  const AdjustableDropdownListTile({
    this.label,
    this.value,
    this.items,
    this.onChanged,
  });

  final String label;
  final String value;
  final List<String> items;
  final ValueChanged<String> onChanged;

  @override
  Widget build(BuildContext context) {
    final int indexOfValue = items.indexOf(value);
    assert(indexOfValue != -1);

    final bool canIncrease = indexOfValue < items.length - 1;
    final bool canDecrease = indexOfValue > 0;

39
    return Semantics(
40 41 42 43 44 45 46
      container: true,
      label: label,
      value: value,
      increasedValue: canIncrease ? _increasedValue : null,
      decreasedValue: canDecrease ? _decreasedValue : null,
      onIncrease: canIncrease ? _performIncrease : null,
      onDecrease: canDecrease ? _performDecrease : null,
47 48 49 50
      child: ExcludeSemantics(
        child: ListTile(
          title: Text(label),
          trailing: DropdownButton<String>(
51 52
            value: value,
            onChanged: onChanged,
53
            items: items.map<DropdownMenuItem<String>>((String item) {
54
              return DropdownMenuItem<String>(
55
                value: item,
56
                child: Text(item),
57 58 59 60
              );
            }).toList(),
          ),
        ),
61
      ),
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    );
  }

  String get _increasedValue {
    final int indexOfValue = items.indexOf(value);
    assert(indexOfValue < items.length - 1);
    return items[indexOfValue + 1];
  }

  String get _decreasedValue {
    final int indexOfValue = items.indexOf(value);
    assert(indexOfValue > 0);
    return items[indexOfValue - 1];
  }

  void _performIncrease() => onChanged(_increasedValue);

  void _performDecrease() => onChanged(_decreasedValue);
}

class AdjustableDropdownExample extends StatefulWidget {
  @override
84
  AdjustableDropdownExampleState createState() => AdjustableDropdownExampleState();
85 86 87 88 89 90 91 92 93
}

class AdjustableDropdownExampleState extends State<AdjustableDropdownExample> {

  final List<String> items = <String>[
    '1 second',
    '5 seconds',
    '15 seconds',
    '30 seconds',
94
    '1 minute',
95 96 97 98 99
  ];
  String timeout;

  @override
  Widget build(BuildContext context) {
100 101 102
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
103 104
          title: const Text('Adjustable DropDown'),
        ),
105
        body: ListView(
106
          children: <Widget>[
107
            AdjustableDropdownListTile(
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
              label: 'Timeout',
              value: timeout ?? items[2],
              items: items,
              onChanged: (String value) {
                setState(() {
                  timeout = value;
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
125
  runApp(AdjustableDropdownExample());
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
}

/*
Sample Catalog

Title: AdjustableDropdownListTile

Summary: A dropdown menu that exposes itself as an "Adjustable" to screen
readers.

Description:
This app presents a dropdown menu to the user that exposes itself as an
"Adjustable" to screen readers (e.g. TalkBack on Android and VoiceOver on iOS).
This allows users of screen readers to cycle through the values of the dropdown
menu by swiping up or down on the screen with one finger (on iOS) or by using
the volume keys (on Android). Depending on the values in the dropdown this
behavior may be more intuitive to screen reader users compared to showing the
classical dropdown overlay on screen to choose a value.

When the screen reader is turned off, the dropdown menu behaves like any
dropdown menu would.

Classes: Semantics

Sample: AdjustableDropdownListTile

*/