如何自定义SearchDelegate(创建自定义搜索字段)

bin*_*112 4 android dart flutter

我一直在尝试将 Flutter 定制SearchDelgate为我想要的搜索字段类型。appBarTheme它有一个名为返回类型的方法ThemeData。通常使用ThemeData您可以更改应用栏主题,但在我的情况下它没有进行任何更改。我可以自定义提示文本样式searchFieldStyle方法,但仅此而已。

这是代码:

class CustomSearchDelegate extends SearchDelegate<Country> {
  @override
  ThemeData appBarTheme(BuildContext context) {
    return ThemeData(
      appBarTheme: AppBarTheme(
        elevation: 0,
        color: themeColor,
        //app bar color I wanted
      ),
    );
  }

  @override
  TextStyle get searchFieldStyle => TextStyle(
        color: whiteTextColor,
        fontWeight: FontWeight.w600,
        fontFamily: GoogleFonts.poppins().fontFamily,
      );

  @override
  List<Widget> buildActions(BuildContext context) {
    return [
        IconButton(
          icon: Icon(
            Icons.close_rounded,
            color: Colors.white,
          ),
          onPressed: () => query = '',
        ),
    ];
  }

  @override
  Widget buildLeading(BuildContext context) {
    return IconButton(
      icon: Icon(
        Icons.arrow_back_ios,
        color: Colors.white,
      ),
      onPressed: () {
        close(context, null);
      },
    );
  }

  @override
  Widget buildResults(BuildContext context) {
    return Column(
      children: [],
    );
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    return Column(
      children: [],
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

如果有人能帮助我解决这个问题,那就太有帮助了。

另外,之前曾提出过类似的问题,但从未得到解答 Flutter create custom search UI extends SearchDelegate

Sid*_*iya 6

我找到了一种按照您想要的方式自定义 flutter search delegate 的方法。您只需复制 flutter 的搜索委托代码,然后自定义您想要的代码即可。

解决办法如下: 1:这是showSearch的代码。

 Container(
                  padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 15.h, bottom: 15.h),
                  decoration: BoxDecoration(borderRadius: BorderRadius.circular(5.r)),
                  child: CustomSearchButton(
                    onTap: () async {
                      final String? result = await showSearchForCustomiseSearchDelegate(
                        context: context,
                        delegate: SearchScreen(
                          hintText: AppLocalizations.of(context)!.searchHere,
                        ),
                      );
                    },
                  ),
                ),   
Run Code Online (Sandbox Code Playgroud)

2:这是定制的flutter searchDelegate的代码。

Widget build(BuildContext context) {
    assert(debugCheckHasMaterialLocalizations(context));
    final ThemeData theme = widget.delegate.appBarTheme(context);
    final String searchFieldLabel = widget.delegate.searchFieldLabel ?? MaterialLocalizations.of(context).searchFieldLabel;
    Widget? body;
    switch (widget.delegate._currentBody) {
      case _SearchBody.suggestions:
        body = KeyedSubtree(
          key: const ValueKey<_SearchBody>(_SearchBody.suggestions),
          child: widget.delegate.buildSuggestions(context),
        );
        break;
      case _SearchBody.results:
        body = KeyedSubtree(
          key: const ValueKey<_SearchBody>(_SearchBody.results),
          child: widget.delegate.buildResults(context),
        );
        break;
      case null:
        break;
    }

    late final String routeName;
    switch (theme.platform) {
      case TargetPlatform.iOS:
      case TargetPlatform.macOS:
        routeName = '';
        break;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        routeName = searchFieldLabel;
    }

    return Semantics(
      explicitChildNodes: true,
      scopesRoute: true,
      namesRoute: true,
      label: routeName,
      child: Theme(
        data: theme,
        child: Scaffold(
          appBar: AppBar(
            elevation: 0,
            automaticallyImplyLeading: false,
            backgroundColor: Colors.transparent,
            leadingWidth: 0,
            titleSpacing: 0,
            //leading: widget.delegate.buildLeading(context),
            title: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                Expanded(flex: 1, child: widget.delegate.buildLeading(context)!),
                Expanded(
                  flex: 6,
                  child: Container(
                    margin: EdgeInsets.only(right: 15.w),
                    decoration: const BoxDecoration(
                      color: AppColors.white,
                    ),
                    child: TextField(
                      controller: widget.delegate._queryTextController,
                      //focusNode: focusNode,
                      onSubmitted: (String _) {
                        widget.delegate.showResults(context);
                      },
                      textInputAction: widget.delegate.textInputAction,
                      keyboardType: widget.delegate.keyboardType,
                      decoration: InputDecoration(
                          fillColor: AppColors.white,
                          filled: true,
                          isDense: true,
                          hintText: searchFieldLabel,
                          hintStyle: TextStyle(fontSize: 14.sp),
                          contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
                          prefixIcon: widget.delegate._queryTextController.text.isNotEmpty
                              ? null
                              : Padding(
                                  padding: EdgeInsets.only(right: 5.w),
                                  child: Image.asset(
                                    AppImages.searchBoxIcon1,
                                    scale: 3.5.sp,
                                  ),
                                ),
                          suffixIcon: widget.delegate._queryTextController.text.isEmpty
                              ? Image.asset(
                                  AppImages.searchBoxIcon2,
                                  scale: 3.5.sp,
                                )
                              : InkWell(
                                  onTap: () {
                                    widget.delegate._queryTextController.clear();
                                  },
                                  child: Image.asset(
                                    AppImages.closeCircle,
                                    scale: 3.5.sp,
                                  ),
                                ),
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(Radius.circular(8.r)),
                            borderSide: const BorderSide(width: 1, color: AppColors.primaryColor),
                          ),
                          enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(Radius.circular(8.r)),
                            borderSide: const BorderSide(width: 1, color: AppColors.white),
                          ),
                          border: OutlineInputBorder(
                            borderSide: const BorderSide(color: AppColors.primaryColor),
                            borderRadius: BorderRadius.all(Radius.circular(8.r)),
                          )),
                    ),

                    // TextField(
                    //   controller: widget.delegate._queryTextController,
                    //   focusNode: focusNode,
                    //   style: theme.textTheme.headline6,
                    //   textInputAction: widget.delegate.textInputAction,
                    //   keyboardType: widget.delegate.keyboardType,
                    //   onSubmitted: (String _) {
                    //     widget.delegate.showResults(context);
                    //   },
                    //   decoration: InputDecoration(hintText: searchFieldLabel),
                    // ),
                  ),
                ),
              ],
            ),
            actions: widget.delegate.buildActions(context),
            bottom: widget.delegate.buildBottom(context),
          ),
          body: AnimatedSwitcher(
            duration: const Duration(milliseconds: 300),
            child: body,
          ),
        ),
      ),
    );
Run Code Online (Sandbox Code Playgroud)

3:这是完整的代码。

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:test_app/constants/app_images.dart';
import 'package:test_app/theme/colors.dart';
import 'package:test_app/widgets/custom_buttons.dart';

class SearchScreen extends SearchDelegate<String> {
  SearchScreen({
    String? hintText,
  }) : super(
          searchFieldLabel: hintText,
          keyboardType: TextInputType.text,
          textInputAction: TextInputAction.search,
        );

  @override
  List<Widget>? buildActions(BuildContext context) {
    return [
      Container(),
    ];
  }

  @override
  Widget? buildLeading(BuildContext context) {
    return CustomBackButton(onTap: () {
      close(context, '');
    });
  }

  @override
  Widget buildResults(BuildContext context) {
    return Container();
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    return Container();
  }
}

Future<T?> showSearchForCustomiseSearchDelegate<T>({
  required BuildContext context,
  required SearchDelegate<T> delegate,
  String? query = '',
  bool useRootNavigator = false,
}) {
  assert(delegate != null);
  assert(context != null);
  assert(useRootNavigator != null);
  delegate.query = query ?? delegate.query;
  delegate._currentBody = _SearchBody.suggestions;
  return Navigator.of(context, rootNavigator: useRootNavigator).push(_SearchPageRoute<T>(
    delegate: delegate,
  ));
}

abstract class SearchDelegate<T> {
  SearchDelegate({
    this.searchFieldLabel,
    this.searchFieldStyle,
    this.searchFieldDecorationTheme,
    this.keyboardType,
    this.textInputAction = TextInputAction.search,
  }) : assert(searchFieldStyle == null || searchFieldDecorationTheme == null);

  Widget buildSuggestions(BuildContext context);

  Widget buildResults(BuildContext context);

  Widget? buildLeading(BuildContext context);

  List<Widget>? buildActions(BuildContext context);

  PreferredSizeWidget? buildBottom(BuildContext context) => null;

  ThemeData appBarTheme(BuildContext context) {
    assert(context != null);
    final ThemeData theme = Theme.of(context);
    final ColorScheme colorScheme = theme.colorScheme;
    assert(theme != null);
    return theme.copyWith(
      appBarTheme: AppBarTheme(
        brightness: colorScheme.brightness,
        backgroundColor: colorScheme.brightness == Brightness.dark ? Colors.grey[900] : Colors.white,
        iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
        textTheme: theme.textTheme,
      ),
      inputDecorationTheme: searchFieldDecorationTheme ??
          InputDecorationTheme(
            hintStyle: searchFieldStyle ?? theme.inputDecorationTheme.hintStyle,
            border: InputBorder.none,
          ),
    );
  }

  String get query => _queryTextController.text;

  set query(String value) {
    assert(query != null);
    _queryTextController.text = value;
    queryTextController.selection = TextSelection.fromPosition(TextPosition(offset: queryTextController.text.length));
  }

  void showResults(BuildContext context) {
    _focusNode?.unfocus();
    currentBody = SearchBody.results;
  }

  void showSuggestions(BuildContext context) {
    assert(_focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
    _focusNode!.requestFocus();
    currentBody = SearchBody.suggestions;
  }

  void close(BuildContext context, T result) {
    _currentBody = null;
    _focusNode?.unfocus();
    Navigator.of(context)
      ..popUntil((Route<dynamic> route) => route == _route)
      ..pop(result);
  }

  final String? searchFieldLabel;

  final TextStyle? searchFieldStyle;

  final InputDecorationTheme? searchFieldDecorationTheme;

  final TextInputType? keyboardType;

  final TextInputAction textInputAction;

  Animation<double> get transitionAnimation => _proxyAnimation;

  // The focus node to use for manipulating focus on the search page. This is
  // managed, owned, and set by the _SearchPageRoute using this delegate.
  FocusNode? _focusNode;

  final TextEditingController _queryTextController = TextEditingController();

  final ProxyAnimation _proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation);

  final ValueNotifier<_SearchBody?> _currentBodyNotifier = ValueNotifier<_SearchBody?>(null);

  SearchBody? get currentBody => _currentBodyNotifier.value;
  set _currentBody(_SearchBody? value) {
    _currentBodyNotifier.value = value;
  }

  SearchPageRoute<T>? route;
}

enum _SearchBody {
  suggestions,

  results,
}

class _SearchPageRoute<T> extends PageRoute<T> {
  _SearchPageRoute({
    required this.delegate,
  }) : assert(delegate != null) {
    assert(
      delegate._route == null,
      'The ${delegate.runtimeType} instance is currently used by another active '
      'search. Please close that search by calling close() on the SearchDelegate '
      'before opening another search with the same delegate instance.',
    );
    delegate._route = this;
  }

  final SearchDelegate<T> delegate;

  @override
  Color? get barrierColor => null;

  @override
  String? get barrierLabel => null;

  @override
  Duration get transitionDuration => const Duration(milliseconds: 300);

  @override
  bool get maintainState => false;

  @override
  Widget buildTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  }

  @override
  Animation<double> createAnimation() {
    final Animation<double> animation = super.createAnimation();
    delegate._proxyAnimation.parent = animation;
    return animation;
  }

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return _SearchPage<T>(
      delegate: delegate,
      animation: animation,
    );
  }

  @override
  void didComplete(T? result) {
    super.didComplete(result);
    assert(delegate._route == this);
    delegate._route = null;
    delegate._currentBody = null;
  }
}

class _SearchPage<T> extends StatefulWidget {
  const _SearchPage({
    required this.delegate,
    required this.animation,
  });

  final SearchDelegate<T> delegate;
  final Animation<double> animation;

  @override
  State<StatefulWidget> createState() => _SearchPageState<T>();
}

class _SearchPageState<T> extends State<_SearchPage<T>> {
  FocusNode focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    widget.delegate._queryTextController.addListener(_onQueryChanged);
    widget.animation.addStatusListener(_onAnimationStatusChanged);
    widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
    focusNode.addListener(_onFocusChanged);
    widget.delegate._focusNode = focusNode;
  }

  @override
  void dispose() {
    super.dispose();
    widget.delegate._queryTextController.removeListener(_onQueryChanged);
    widget.animation.removeStatusListener(_onAnimationStatusChanged);
    widget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
    widget.delegate._focusNode = null;
    focusNode.dispose();
  }

  void _onAnimationStatusChanged(AnimationStatus status) {
    if (status != AnimationStatus.completed) {
      return;
    }
    widget.animation.removeStatusListener(_onAnimationStatusChanged);
    if (widget.delegate._currentBody == _SearchBody.suggestions) {
      focusNode.requestFocus();
    }
  }

  @override
  void didUpdateWidget(_SearchPage<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.delegate != oldWidget.delegate) {
      oldWidget.delegate._queryTextController.removeListener(_onQueryChanged);
      widget.delegate._queryTextController.addListener(_onQueryChanged);
      oldWidget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
      widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
      oldWidget.delegate._focusNode = null;
      widget.delegate._focusNode = focusNode;
    }
  }

  void _onFocusChanged() {
    if (focusNode.hasFocus && widget.delegate._currentBody != _SearchBody.suggestions) {
      widget.delegate.showSuggestions(context);
    }
  }

  void _onQueryChanged() {
    setState(() {
      // rebuild ourselves because query changed.
    });
  }

  void _onSearchBodyChanged() {
    setState(() {
      // rebuild ourselves because search body changed.
    });
  }

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterialLocalizations(context));
    final ThemeData theme = widget.delegate.appBarTheme(context);
    final String searchFieldLabel = widget.delegate.searchFieldLabel ?? MaterialLocalizations.of(context).searchFieldLabel;
    Widget? body;
    switch (widget.delegate._currentBody) {
      case _SearchBody.suggestions:
        body = KeyedSubtree(
          key: const ValueKey<_SearchBody>(_SearchBody.suggestions),
          child: widget.delegate.buildSuggestions(context),
        );
        break;
      case _SearchBody.results:
        body = KeyedSubtree(
          key: const ValueKey<_SearchBody>(_SearchBody.results),
          child: widget.delegate.buildResults(context),
        );
        break;
      case null:
        break;
    }

    late final String routeName;
    switch (theme.platform) {
      case TargetPlatform.iOS:
      case TargetPlatform.macOS:
        routeName = '';
        break;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        routeName = searchFieldLabel;
    }

    return Semantics(
      explicitChildNodes: true,
      scopesRoute: true,
      namesRoute: true,
      label: routeName,
      child: Theme(
        data: theme,
        child: Scaffold(
          appBar: AppBar(
            elevation: 0,
            automaticallyImplyLeading: false,
            backgroundColor: Colors.transparent,
            leadingWidth: 0,
            titleSpacing: 0,
            //leading: widget.delegate.buildLeading(context),
            title: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                Expanded(flex: 1, child: widget.delegate.buildLeading(context)!),
                Expanded(
                  flex: 6,
                  child: Container(
                    margin: EdgeInsets.only(right: 15.w),
                    decoration: const BoxDecoration(
                      color: AppColors.white,
                    ),
                    child: TextField(
                      controller: widget.delegate._queryTextController,
                      //focusNode: focusNode,
                      onSubmitted: (String _) {
                        widget.delegate.showResults(context);
                      },
                      textInputAction: widget.delegate.textInputAction,
                      keyboardType: widget.delegate.keyboardType,
                      decoration: InputDecoration(
                          fillColor: AppColors.white,
                          filled: true,
                          isDense: true,
                          hintText: searchFieldLabel,
                          hintStyle: TextStyle(fontSize: 14.sp),
                          contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
                          prefixIcon: widget.delegate._queryTextController.text.isNotEmpty
                              ? null
                              : Padding(
                                  padding: EdgeInsets.only(right: 5.w),
                                  child: Image.asset(
                                    AppImages.searchBoxIcon1,
                                    scale: 3.5.sp,
                                  ),
                                ),
                          suffixIcon: widget.delegate._queryTextController.text.isEmpty
                              ? Image.asset(
                                  AppImages.searchBoxIcon2,
                                  scale: 3.5.sp,
                                )
                              : InkWell(
                                  onTap: () {
                                    widget.delegate._queryTextController.clear();
                                  },
                                  child: Image.asset(
                                    AppImages.closeCircle,
                                    scale: 3.5.sp,
                                  ),
                                ),
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(Radius.circular(8.r)),
                            borderSide: const BorderSide(width: 1, color: AppColors.primaryColor),
                          ),
                          enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(Radius.circular(8.r)),