| // 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. |
| |
| import 'package:flutter/widgets.dart'; |
| |
| import 'color_scheme.dart'; |
| import 'colors.dart'; |
| import 'constants.dart'; |
| import 'ink_well.dart'; |
| import 'input_border.dart'; |
| import 'input_decorator.dart'; |
| import 'material.dart'; |
| import 'material_state.dart'; |
| import 'search_bar_theme.dart'; |
| import 'text_field.dart'; |
| import 'text_theme.dart'; |
| import 'theme.dart'; |
| |
| /// A Material Design search bar. |
| /// |
| /// Search bars include a [leading] Search icon, a text input field and optional |
| /// [trailing] icons. A search bar is typically used to open a search view. |
| /// It is the default trigger for a search view. |
| /// |
| /// For [TextDirection.ltr], the [leading] widget is on the left side of the bar. |
| /// It should contain either a navigational action (such as a menu or up-arrow) |
| /// or a non-functional search icon. |
| /// |
| /// The [trailing] is an optional list that appears at the other end of |
| /// the search bar. Typically only one or two action icons are included. |
| /// These actions can represent additional modes of searching (like voice search), |
| /// a separate high-level action (such as current location) or an overflow menu. |
| class SearchBar extends StatefulWidget { |
| /// Creates a Material Design search bar. |
| const SearchBar({ |
| super.key, |
| this.controller, |
| this.focusNode, |
| this.hintText, |
| this.leading, |
| this.trailing, |
| this.onTap, |
| this.onChanged, |
| this.constraints, |
| this.elevation, |
| this.backgroundColor, |
| this.shadowColor, |
| this.surfaceTintColor, |
| this.overlayColor, |
| this.side, |
| this.shape, |
| this.padding, |
| this.textStyle, |
| this.hintStyle, |
| }); |
| |
| /// Controls the text being edited in the search bar's text field. |
| /// |
| /// If null, this widget will create its own [TextEditingController]. |
| final TextEditingController? controller; |
| |
| /// {@macro flutter.widgets.Focus.focusNode} |
| final FocusNode? focusNode; |
| |
| /// Text that suggests what sort of input the field accepts. |
| /// |
| /// Displayed at the same location on the screen where text may be entered |
| /// when the input is empty. |
| /// |
| /// Defaults to null. |
| final String? hintText; |
| |
| /// A widget to display before the text input field. |
| /// |
| /// Typically the [leading] widget is an [Icon] or an [IconButton]. |
| final Widget? leading; |
| |
| /// A list of Widgets to display in a row after the text field. |
| /// |
| /// Typically these actions can represent additional modes of searching |
| /// (like voice search), an avatar, a separate high-level action (such as |
| /// current location) or an overflow menu. There should not be more than |
| /// two trailing actions. |
| final Iterable<Widget>? trailing; |
| |
| /// Called when the user taps this search bar. |
| final GestureTapCallback? onTap; |
| |
| /// Invoked upon user input. |
| final ValueChanged<String>? onChanged; |
| |
| /// Optional size constraints for the search bar. |
| /// |
| /// If null, the value of [SearchBarThemeData.constraints] will be used. If |
| /// this is also null, then the constraints defaults to: |
| /// ```dart |
| /// const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: 56.0) |
| /// ``` |
| final BoxConstraints? constraints; |
| |
| /// The elevation of the search bar's [Material]. |
| /// |
| /// If null, the value of [SearchBarThemeData.elevation] will be used. If this |
| /// is also null, then default value is 8.0. |
| final MaterialStateProperty<double?>? elevation; |
| |
| /// The search bar's background fill color. |
| /// |
| /// If null, the value of [SearchBarThemeData.backgroundColor] will be used. |
| /// If this is also null, then the default value is [ColorScheme.surface]. |
| final MaterialStateProperty<Color?>? backgroundColor; |
| |
| /// The shadow color of the search bar's [Material]. |
| /// |
| /// If null, the value of [SearchBarThemeData.shadowColor] will be used. |
| /// If this is also null, then the default value is [ColorScheme.shadow]. |
| final MaterialStateProperty<Color?>? shadowColor; |
| |
| /// The surface tint color of the search bar's [Material]. |
| /// |
| /// See [Material.surfaceTintColor] for more details. |
| /// |
| /// If null, the value of [SearchBarThemeData.surfaceTintColor] will be used. |
| /// If this is also null, then the default value is [ColorScheme.surfaceTint]. |
| final MaterialStateProperty<Color?>? surfaceTintColor; |
| |
| /// The highlight color that's typically used to indicate that |
| /// the search bar is focused, hovered, or pressed. |
| final MaterialStateProperty<Color?>? overlayColor; |
| |
| /// The color and weight of the search bar's outline. |
| /// |
| /// This value is combined with [shape] to create a shape decorated |
| /// with an outline. |
| /// |
| /// If null, the value of [SearchBarThemeData.side] will be used. If this is |
| /// also null, the search bar doesn't have a side by default. |
| final MaterialStateProperty<BorderSide?>? side; |
| |
| /// The shape of the search bar's underlying [Material]. |
| /// |
| /// This shape is combined with [side] to create a shape decorated |
| /// with an outline. |
| /// |
| /// If null, the value of [SearchBarThemeData.shape] will be used. |
| /// If this is also null, then the default value is 16.0 horizontally. |
| /// Defaults to [StadiumBorder]. |
| final MaterialStateProperty<OutlinedBorder?>? shape; |
| |
| /// The padding between the search bar's boundary and its contents. |
| /// |
| /// If null, the value of [SearchBarThemeData.padding] will be used. |
| /// If this is also null, then the default value is 16.0 horizontally. |
| final MaterialStateProperty<EdgeInsetsGeometry?>? padding; |
| |
| /// The style to use for the text being edited. |
| /// |
| /// If null, defaults to the `bodyLarge` text style from the current [Theme]. |
| /// The default text color is [ColorScheme.onSurface]. |
| final MaterialStateProperty<TextStyle?>? textStyle; |
| |
| /// The style to use for the [hintText]. |
| /// |
| /// If null, the value of [SearchBarThemeData.hintStyle] will be used. If this |
| /// is also null, the value of [textStyle] will be used. If this is also null, |
| /// defaults to the `bodyLarge` text style from the current [Theme]. |
| /// The default text color is [ColorScheme.onSurfaceVariant]. |
| final MaterialStateProperty<TextStyle?>? hintStyle; |
| |
| @override |
| State<SearchBar> createState() => _SearchBarState(); |
| } |
| |
| class _SearchBarState extends State<SearchBar> { |
| late final MaterialStatesController _internalStatesController; |
| late final FocusNode _focusNode; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _internalStatesController = MaterialStatesController(); |
| _internalStatesController.addListener(() { |
| setState(() {}); |
| }); |
| _focusNode = widget.focusNode ?? FocusNode(); |
| } |
| |
| @override |
| void dispose() { |
| _internalStatesController.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final TextDirection textDirection = Directionality.of(context); |
| final ColorScheme colorScheme = Theme.of(context).colorScheme; |
| final IconThemeData iconTheme = IconTheme.of(context); |
| final SearchBarThemeData searchBarTheme = SearchBarTheme.of(context); |
| final SearchBarThemeData defaults = _SearchBarDefaultsM3(context); |
| |
| T? resolve<T>( |
| MaterialStateProperty<T>? widgetValue, |
| MaterialStateProperty<T>? themeValue, |
| MaterialStateProperty<T>? defaultValue, |
| ) { |
| final Set<MaterialState> states = _internalStatesController.value; |
| return widgetValue?.resolve(states) ?? themeValue?.resolve(states) ?? defaultValue?.resolve(states); |
| } |
| |
| final TextStyle? effectiveTextStyle = resolve<TextStyle?>(widget.textStyle, searchBarTheme.textStyle, defaults.textStyle); |
| final double? effectiveElevation = resolve<double?>(widget.elevation, searchBarTheme.elevation, defaults.elevation); |
| final Color? effectiveShadowColor = resolve<Color?>(widget.shadowColor, searchBarTheme.shadowColor, defaults.shadowColor); |
| final Color? effectiveBackgroundColor = resolve<Color?>(widget.backgroundColor, searchBarTheme.backgroundColor, defaults.backgroundColor); |
| final Color? effectiveSurfaceTintColor = resolve<Color?>(widget.surfaceTintColor, searchBarTheme.surfaceTintColor, defaults.surfaceTintColor); |
| final OutlinedBorder? effectiveShape = resolve<OutlinedBorder?>(widget.shape, searchBarTheme.shape, defaults.shape); |
| final BorderSide? effectiveSide = resolve<BorderSide?>(widget.side, searchBarTheme.side, defaults.side); |
| final EdgeInsetsGeometry? effectivePadding = resolve<EdgeInsetsGeometry?>(widget.padding, searchBarTheme.padding, defaults.padding); |
| final MaterialStateProperty<Color?>? effectiveOverlayColor = widget.overlayColor ?? searchBarTheme.overlayColor ?? defaults.overlayColor; |
| |
| final Set<MaterialState> states = _internalStatesController.value; |
| final TextStyle? effectiveHintStyle = widget.hintStyle?.resolve(states) |
| ?? searchBarTheme.hintStyle?.resolve(states) |
| ?? widget.textStyle?.resolve(states) |
| ?? searchBarTheme.textStyle?.resolve(states) |
| ?? defaults.hintStyle?.resolve(states); |
| |
| final bool isDark = Theme.of(context).brightness == Brightness.dark; |
| bool isIconThemeColorDefault(Color? color) { |
| if (isDark) { |
| return color == kDefaultIconLightColor; |
| } |
| return color == kDefaultIconDarkColor; |
| } |
| |
| Widget? leading; |
| if (widget.leading != null) { |
| leading = IconTheme.merge( |
| data: isIconThemeColorDefault(iconTheme.color) |
| ? IconThemeData(color: colorScheme.onSurface) |
| : iconTheme, |
| child: widget.leading!, |
| ); |
| } |
| |
| List<Widget>? trailing; |
| if (widget.trailing != null) { |
| trailing = widget.trailing?.map((Widget trailing) => IconTheme.merge( |
| data: isIconThemeColorDefault(iconTheme.color) |
| ? IconThemeData(color: colorScheme.onSurfaceVariant) |
| : iconTheme, |
| child: trailing, |
| )).toList(); |
| } |
| |
| return SafeArea( |
| child: ConstrainedBox( |
| constraints: widget.constraints ?? searchBarTheme.constraints ?? defaults.constraints!, |
| child: Material( |
| elevation: effectiveElevation!, |
| shadowColor: effectiveShadowColor, |
| color: effectiveBackgroundColor, |
| surfaceTintColor: effectiveSurfaceTintColor, |
| shape: effectiveShape?.copyWith(side: effectiveSide), |
| child: InkWell( |
| onTap: () { |
| widget.onTap?.call(); |
| _focusNode.requestFocus(); |
| }, |
| overlayColor: effectiveOverlayColor, |
| customBorder: effectiveShape?.copyWith(side: effectiveSide), |
| statesController: _internalStatesController, |
| child: Padding( |
| padding: effectivePadding!, |
| child: Row( |
| textDirection: textDirection, |
| children: <Widget>[ |
| if (leading != null) leading, |
| Expanded( |
| child: IgnorePointer( |
| child: Padding( |
| padding: effectivePadding, |
| child: TextField( |
| focusNode: _focusNode, |
| onChanged: widget.onChanged, |
| controller: widget.controller, |
| style: effectiveTextStyle, |
| decoration: InputDecoration( |
| border: InputBorder.none, |
| hintText: widget.hintText, |
| hintStyle: effectiveHintStyle, |
| ), |
| ), |
| ), |
| ) |
| ), |
| if (trailing != null) ...trailing, |
| ], |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - SearchBar |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| // Token database version: v0_158 |
| |
| class _SearchBarDefaultsM3 extends SearchBarThemeData { |
| _SearchBarDefaultsM3(this.context); |
| |
| final BuildContext context; |
| late final ColorScheme _colors = Theme.of(context).colorScheme; |
| late final TextTheme _textTheme = Theme.of(context).textTheme; |
| |
| @override |
| MaterialStateProperty<Color?>? get backgroundColor => |
| MaterialStatePropertyAll<Color>(_colors.surface); |
| |
| @override |
| MaterialStateProperty<double>? get elevation => |
| const MaterialStatePropertyAll<double>(6.0); |
| |
| @override |
| MaterialStateProperty<Color>? get shadowColor => |
| MaterialStatePropertyAll<Color>(_colors.shadow); |
| |
| @override |
| MaterialStateProperty<Color>? get surfaceTintColor => |
| MaterialStatePropertyAll<Color>(_colors.surfaceTint); |
| |
| @override |
| MaterialStateProperty<Color?>? get overlayColor => |
| MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.pressed)) { |
| return _colors.onSurface.withOpacity(0.12); |
| } |
| if (states.contains(MaterialState.hovered)) { |
| return _colors.onSurface.withOpacity(0.08); |
| } |
| if (states.contains(MaterialState.focused)) { |
| return Colors.transparent; |
| } |
| return Colors.transparent; |
| }); |
| |
| // No default side |
| |
| @override |
| MaterialStateProperty<OutlinedBorder>? get shape => |
| const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder()); |
| |
| @override |
| MaterialStateProperty<EdgeInsetsGeometry>? get padding => |
| const MaterialStatePropertyAll<EdgeInsetsGeometry>(EdgeInsets.symmetric(horizontal: 8.0)); |
| |
| @override |
| MaterialStateProperty<TextStyle?> get textStyle => |
| MaterialStatePropertyAll<TextStyle?>(_textTheme.bodyLarge?.copyWith(color: _colors.onSurface)); |
| |
| @override |
| MaterialStateProperty<TextStyle?> get hintStyle => |
| MaterialStatePropertyAll<TextStyle?>(_textTheme.bodyLarge?.copyWith(color: _colors.onSurfaceVariant)); |
| |
| @override |
| BoxConstraints get constraints => |
| const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: 56.0); |
| } |
| |
| // END GENERATED TOKEN PROPERTIES - SearchBar |