简体   繁体   English

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

[英]How to customize SearchDelegate(Create custom search Field)

I've been trying to customize Flutter SearchDelgate to the type of search field I want it to be.我一直在尝试将 Flutter SearchDelgate自定义为我想要的搜索字段类型。 It got a method named appBarTheme with return type ThemeData .它有一个名为appBarTheme的方法,返回类型为ThemeData Usually using ThemeData you can change the appbar theme but it's not making any change in my case.通常使用ThemeData您可以更改 appbar 主题,但在我的情况下并没有做出任何改变。 I am able to customize the hint text style searchFieldStyle method but nothing more.我可以自定义提示文本样式searchFieldStyle方法,但仅此而已。

here is code:这是代码:

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: [],
    );
  }
}

It would be super helpful if someone could help me out with this.如果有人可以帮助我解决这个问题,那将是非常有帮助的。

also, a similar question has been raised before but never got answered Flutter create custom search UI extends SearchDelegate此外,之前提出过类似的问题,但从未得到回答Flutter 创建自定义搜索 UI 扩展 SearchDelegate

Unfortunately, you do not have full control over the Theme of the AppBar in the SearchDelegate since some of the theme property values that you specify in the appBarTheme are not assigned to the app bar widget used in SearchDelegate .不幸的是,您无法完全控制SearchDelegateAppBar的主题,因为您在appBarTheme中指定的某些主题属性值并未分配给SearchDelegate中使用的应用栏小部件。 You can take a look at the source code.你可以看看源代码。 It only takes the values specified in the ThemeData specified in MaterialApp theme property.它仅采用MaterialApp主题属性中指定的 ThemeData 中指定的值。 In my case, I needed to change the cursor color but changing the color in the MaterialApp would also modify the color in TextFields used elsewhere.就我而言,我需要更改 cursor 颜色,但更改MaterialApp中的颜色也会修改其他地方使用的 TextFields 中的颜色。

One solution is you can change the color before even opening the SearchDelegate ie before showSearch and change it back again to the original color after navigating back from showSearch .一种解决方案是您甚至可以在打开SearchDelegate之前更改颜色,即在showSearch之前,并在从showSearch导航回来后再次将其更改回原始颜色。

I found one way to customize flutter search delegate the way you want.我找到了一种按照您想要的方式自定义 flutter 搜索委托的方法。 you just have to copy flutter's search delegates code and then customize the code you want.你只需要复制 Flutter 的搜索委托代码,然后自定义你想要的代码。

Here is the Solution: 1: this is the code of showSearch.解决方法如下: 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,
                        ),
                      );
                    },
                  ),
                ),   

2: this is the code of customised flutter searchDelegate. 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,
          ),
        ),
      ),
    );

3: Here is the full code. 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)),
                            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,
          ),
        ),
      ),
    );
  }
}

4: OUTPUT Output video 4:OUTPUT Output视频

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM