autocomplete.dart 6.09 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
/// {@tool dartpad}
16 17 18
/// This example shows how to create a very basic Autocomplete widget using the
/// default UI.
///
19
/// ** See code in examples/api/lib/material/autocomplete/autocomplete.0.dart **
20 21
/// {@end-tool}
///
22
/// {@tool dartpad}
23 24 25
/// This example shows how to create an Autocomplete widget with a custom type.
/// Try searching with text from the name or email field.
///
26
/// ** See code in examples/api/lib/material/autocomplete/autocomplete.1.dart **
27 28 29 30 31 32 33 34 35
/// {@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({
36
    super.key,
37 38 39 40
    required this.optionsBuilder,
    this.displayStringForOption = RawAutocomplete.defaultStringForOption,
    this.fieldViewBuilder = _defaultFieldViewBuilder,
    this.onSelected,
41
    this.optionsMaxHeight = 200.0,
42
    this.optionsViewBuilder,
43
    this.initialValue,
44
  }) : assert(displayStringForOption != null),
45
       assert(optionsBuilder != null);
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

  /// {@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;

68 69 70 71 72 73 74 75
  /// 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;

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

79 80 81 82 83 84 85 86 87 88 89 90 91
  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,
92
      initialValue: initialValue,
93 94 95 96 97 98
      optionsBuilder: optionsBuilder,
      optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
        return _AutocompleteOptions<T>(
          displayStringForOption: displayStringForOption,
          onSelected: onSelected,
          options: options,
99
          maxOptionsHeight: optionsMaxHeight,
100 101 102 103 104 105 106 107 108 109 110 111 112
        );
      },
      onSelected: onSelected,
    );
  }
}

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

  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({
136
    super.key,
137 138 139
    required this.displayStringForOption,
    required this.onSelected,
    required this.options,
140
    required this.maxOptionsHeight,
141
  });
142 143 144 145 146 147

  final AutocompleteOptionToString<T> displayStringForOption;

  final AutocompleteOnSelected<T> onSelected;

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

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.topLeft,
      child: Material(
        elevation: 4.0,
156 157
        child: ConstrainedBox(
          constraints: BoxConstraints(maxHeight: maxOptionsHeight),
158
          child: ListView.builder(
159
            padding: EdgeInsets.zero,
160
            shrinkWrap: true,
161 162 163 164 165 166 167
            itemCount: options.length,
            itemBuilder: (BuildContext context, int index) {
              final T option = options.elementAt(index);
              return InkWell(
                onTap: () {
                  onSelected(option);
                },
168 169 170 171
                child: Builder(
                  builder: (BuildContext context) {
                    final bool highlight = AutocompleteHighlightedOption.of(context) == index;
                    if (highlight) {
172
                      SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
173 174 175 176 177 178 179 180 181
                        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)),
                    );
                  }
182 183 184 185 186 187 188 189 190
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}