autocomplete.dart 6.08 KB
Newer Older
1 2 3 4
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/scheduler.dart';
6 7 8 9 10
import 'package:flutter/widgets.dart';

import 'ink_well.dart';
import 'material.dart';
import 'text_form_field.dart';
11
import 'theme.dart';
12 13 14

/// {@macro flutter.widgets.RawAutocomplete.RawAutocomplete}
///
15 16
/// {@youtube 560 315 https://www.youtube.com/watch?v=-Nny8kzW380}
///
17
/// {@tool dartpad}
18 19 20
/// This example shows how to create a very basic Autocomplete widget using the
/// default UI.
///
21
/// ** See code in examples/api/lib/material/autocomplete/autocomplete.0.dart **
22 23
/// {@end-tool}
///
24
/// {@tool dartpad}
25 26 27
/// This example shows how to create an Autocomplete widget with a custom type.
/// Try searching with text from the name or email field.
///
28
/// ** See code in examples/api/lib/material/autocomplete/autocomplete.1.dart **
29 30 31 32 33 34 35 36 37
/// {@end-tool}
///
/// See also:
///
///  * [RawAutocomplete], which is what Autocomplete is built upon, and which
///    contains more detailed examples.
class Autocomplete<T extends Object> extends StatelessWidget {
  /// Creates an instance of [Autocomplete].
  const Autocomplete({
38
    super.key,
39 40 41 42
    required this.optionsBuilder,
    this.displayStringForOption = RawAutocomplete.defaultStringForOption,
    this.fieldViewBuilder = _defaultFieldViewBuilder,
    this.onSelected,
43
    this.optionsMaxHeight = 200.0,
44
    this.optionsViewBuilder,
45
    this.initialValue,
46
  });
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

  /// {@macro flutter.widgets.RawAutocomplete.displayStringForOption}
  final AutocompleteOptionToString<T> displayStringForOption;

  /// {@macro flutter.widgets.RawAutocomplete.fieldViewBuilder}
  ///
  /// If not provided, will build a standard Material-style text field by
  /// default.
  final AutocompleteFieldViewBuilder fieldViewBuilder;

  /// {@macro flutter.widgets.RawAutocomplete.onSelected}
  final AutocompleteOnSelected<T>? onSelected;

  /// {@macro flutter.widgets.RawAutocomplete.optionsBuilder}
  final AutocompleteOptionsBuilder<T> optionsBuilder;

  /// {@macro flutter.widgets.RawAutocomplete.optionsViewBuilder}
  ///
  /// If not provided, will build a standard Material-style list of results by
  /// default.
  final AutocompleteOptionsViewBuilder<T>? optionsViewBuilder;

69 70 71 72 73 74 75 76
  /// The maximum height used for the default Material options list widget.
  ///
  /// When [optionsViewBuilder] is `null`, this property sets the maximum height
  /// that the options widget can occupy.
  ///
  /// The default value is set to 200.
  final double optionsMaxHeight;

77 78 79
  /// {@macro flutter.widgets.RawAutocomplete.initialValue}
  final TextEditingValue? initialValue;

80 81 82 83 84 85 86 87 88 89 90 91 92
  static Widget _defaultFieldViewBuilder(BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
    return _AutocompleteField(
      focusNode: focusNode,
      textEditingController: textEditingController,
      onFieldSubmitted: onFieldSubmitted,
    );
  }

  @override
  Widget build(BuildContext context) {
    return RawAutocomplete<T>(
      displayStringForOption: displayStringForOption,
      fieldViewBuilder: fieldViewBuilder,
93
      initialValue: initialValue,
94 95 96 97 98 99
      optionsBuilder: optionsBuilder,
      optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
        return _AutocompleteOptions<T>(
          displayStringForOption: displayStringForOption,
          onSelected: onSelected,
          options: options,
100
          maxOptionsHeight: optionsMaxHeight,
101 102 103 104 105 106 107 108 109 110 111 112 113
        );
      },
      onSelected: onSelected,
    );
  }
}

// The default Material-style Autocomplete text field.
class _AutocompleteField extends StatelessWidget {
  const _AutocompleteField({
    required this.focusNode,
    required this.textEditingController,
    required this.onFieldSubmitted,
114
  });
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

  final FocusNode focusNode;

  final VoidCallback onFieldSubmitted;

  final TextEditingController textEditingController;

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: textEditingController,
      focusNode: focusNode,
      onFieldSubmitted: (String value) {
        onFieldSubmitted();
      },
    );
  }
}

// The default Material-style Autocomplete options.
class _AutocompleteOptions<T extends Object> extends StatelessWidget {
  const _AutocompleteOptions({
137
    super.key,
138 139 140
    required this.displayStringForOption,
    required this.onSelected,
    required this.options,
141
    required this.maxOptionsHeight,
142
  });
143 144 145 146 147 148

  final AutocompleteOptionToString<T> displayStringForOption;

  final AutocompleteOnSelected<T> onSelected;

  final Iterable<T> options;
149
  final double maxOptionsHeight;
150 151 152 153 154 155 156

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.topLeft,
      child: Material(
        elevation: 4.0,
157 158
        child: ConstrainedBox(
          constraints: BoxConstraints(maxHeight: maxOptionsHeight),
159
          child: ListView.builder(
160
            padding: EdgeInsets.zero,
161
            shrinkWrap: true,
162 163 164 165 166 167 168
            itemCount: options.length,
            itemBuilder: (BuildContext context, int index) {
              final T option = options.elementAt(index);
              return InkWell(
                onTap: () {
                  onSelected(option);
                },
169 170 171 172
                child: Builder(
                  builder: (BuildContext context) {
                    final bool highlight = AutocompleteHighlightedOption.of(context) == index;
                    if (highlight) {
173
                      SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
174 175 176 177 178 179 180 181 182
                        Scrollable.ensureVisible(context, alignment: 0.5);
                      });
                    }
                    return Container(
                      color: highlight ? Theme.of(context).focusColor : null,
                      padding: const EdgeInsets.all(16.0),
                      child: Text(displayStringForOption(option)),
                    );
                  }
183 184 185 186 187 188 189 190 191
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}