<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

      Flutter 借助SearchDelegate實現搜索頁面,實現搜索建議、搜索結果,解決IOS拼音問題

      搜索界面使用Flutter自帶的SearchDelegate組件實現,通過魔改實現如下效果:

      1. 搜素建議
      2. 搜索結果,支持刷新和加載更多
      3. IOS中文輸入拼音問題

      界面預覽

      拷貝源碼

      將SearchDelegate的源碼拷貝一份,修改內容如下:

      import 'package:flutter/material.dart';
      import 'package:flutter/services.dart';
      
      /// 修改此處為 showMySearch
      Future<T?> showMySearch<T>({
        required BuildContext context,
        required MySearchDelegate<T> delegate,
        String? query = '',
        bool useRootNavigator = false,
      }) {
        delegate.query = query ?? delegate.query;
        delegate._currentBody = _SearchBody.suggestions;
        return Navigator.of(context, rootNavigator: useRootNavigator)
            .push(_SearchPageRoute<T>(
          delegate: delegate,
        ));
      }
      
      /// https://juejin.cn/post/7090374603951833118
      abstract class MySearchDelegate<T> {
        MySearchDelegate({
          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);
      
        bool? automaticallyImplyLeading;
      
        double? leadingWidth;
      
        List<Widget>? buildActions(BuildContext context);
      
        PreferredSizeWidget? buildBottom(BuildContext context) => null;
      
        Widget? buildFlexibleSpace(BuildContext context) => null;
      
        ThemeData appBarTheme(BuildContext context) {
          final ThemeData theme = Theme.of(context);
          final ColorScheme colorScheme = theme.colorScheme;
          return theme.copyWith(
            appBarTheme: AppBarTheme(
              systemOverlayStyle: colorScheme.brightness == Brightness.dark
                  ? SystemUiOverlayStyle.light
                  : SystemUiOverlayStyle.dark,
              backgroundColor: colorScheme.brightness == Brightness.dark
                  ? Colors.grey[900]
                  : Colors.white,
              iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
              titleTextStyle: theme.textTheme.titleLarge,
              toolbarTextStyle: theme.textTheme.bodyMedium,
            ),
            inputDecorationTheme: searchFieldDecorationTheme ??
                InputDecorationTheme(
                  hintStyle: searchFieldStyle ?? theme.inputDecorationTheme.hintStyle,
                  border: InputBorder.none,
                ),
          );
        }
      
        String get query => _queryTextController.completeText;
      
        set query(String value) {
          _queryTextController.completeText = value; // 更新實際搜索內容
          _queryTextController.text = value; // 更新輸入框內容
          if (_queryTextController.text.isNotEmpty) {
            _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;
      
        FocusNode? _focusNode;
      
        final ChinaTextEditController _queryTextController = ChinaTextEditController();
      
        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;
      
        /// Releases the resources.
        @mustCallSuper
        void dispose() {
          _currentBodyNotifier.dispose();
          _focusNode?.dispose();
          _queryTextController.dispose();
          _proxyAnimation.parent = null;
        }
      }
      
      /// search page.
      enum _SearchBody {
        suggestions,
      
        results,
      }
      
      class _SearchPageRoute<T> extends PageRoute<T> {
        _SearchPageRoute({
          required this.delegate,
        }) {
          assert(
            delegate._route == null,
            'The ${delegate.runtimeType} instance is currently used by another active '
            'search. Please close that search by calling close() on the MySearchDelegate '
            'before opening another search with the same delegate instance.',
          );
          delegate._route = this;
        }
      
        final MySearchDelegate<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 MySearchDelegate<T> delegate;
        final Animation<double> animation;
      
        @override
        State<StatefulWidget> createState() => _SearchPageState<T>();
      }
      
      class _SearchPageState<T> extends State<_SearchPage<T>> {
        // This node is owned, but not hosted by, the search page. Hosting is done by
        // the text field.
        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),
              );
            case _SearchBody.results:
              body = KeyedSubtree(
                key: const ValueKey<_SearchBody>(_SearchBody.results),
                child: widget.delegate.buildResults(context),
              );
            case null:
              break;
          }
      
          late final String routeName;
          switch (theme.platform) {
            case TargetPlatform.iOS:
            case TargetPlatform.macOS:
              routeName = '';
            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(
                  leadingWidth: widget.delegate.leadingWidth,
                  automaticallyImplyLeading:
                      widget.delegate.automaticallyImplyLeading ?? true,
                  leading: widget.delegate.buildLeading(context),
                  title: TextField(
                    controller: widget.delegate._queryTextController,
                    focusNode: focusNode,
                    style: widget.delegate.searchFieldStyle ??
                        theme.textTheme.titleLarge,
                    textInputAction: widget.delegate.textInputAction,
                    keyboardType: widget.delegate.keyboardType,
                    onSubmitted: (String _) => widget.delegate.showResults(context),
                    decoration: InputDecoration(hintText: searchFieldLabel),
                  ),
                  flexibleSpace: widget.delegate.buildFlexibleSpace(context),
                  actions: widget.delegate.buildActions(context),
                  bottom: widget.delegate.buildBottom(context),
                ),
                body: AnimatedSwitcher(
                  duration: const Duration(milliseconds: 300),
                  child: body,
                ),
              ),
            ),
          );
        }
      }
      
      class ChinaTextEditController extends TextEditingController {
        ///拼音輸入完成后的文字
        var completeText = '';
      
        @override
        TextSpan buildTextSpan(
            {required BuildContext context,
            TextStyle? style,
            required bool withComposing}) {
          ///拼音輸入完成
          if (!value.composing.isValid || !withComposing) {
            if (completeText != value.text) {
              completeText = value.text;
              WidgetsBinding.instance.addPostFrameCallback((_) {
                notifyListeners();
              });
            }
            return TextSpan(style: style, text: text);
          }
      
          ///返回輸入樣式,可自定義樣式
          final TextStyle composingStyle = style?.merge(
            const TextStyle(decoration: TextDecoration.underline),
          ) ?? const TextStyle(decoration: TextDecoration.underline);
          return TextSpan(style: style, children: <TextSpan>[
            TextSpan(text: value.composing.textBefore(value.text)),
            TextSpan(
              style: composingStyle,
              text: value.composing.isValid && !value.composing.isCollapsed
                  ? value.composing.textInside(value.text)
                  : "",
            ),
            TextSpan(text: value.composing.textAfter(value.text)),
          ]);
        }
      }
      
      

      實現搜索

      創建SearchPage繼承MySearchDelegate,修改樣式,實現頁面。需要重寫下面5個方法

      • appBarTheme:修改搜索樣式
      • buildActions:搜索框右側的方法
      • buildLeading:搜索框左側的返回按鈕
      • buildResults:搜索結果
      • buildSuggestions:搜索建議
      import 'package:e_book_clone/pages/search/MySearchDelegate.dart';
      import 'package:flutter/src/material/theme_data.dart';
      import 'package:flutter/src/widgets/framework.dart';
      
      class Demo extends MySearchDelegate {
      
        @override
        ThemeData appBarTheme(BuildContext context) {
          // TODO: implement appBarTheme
          return super.appBarTheme(context);
        }
        
        @override
        List<Widget>? buildActions(BuildContext context) {
          // TODO: implement buildActions
          throw UnimplementedError();
        }
      
        @override
        Widget? buildLeading(BuildContext context) {
          // TODO: implement buildLeading
          throw UnimplementedError();
        }
      
        @override
        Widget buildResults(BuildContext context) {
          // TODO: implement buildResults
          throw UnimplementedError();
        }
      
        @override
        Widget buildSuggestions(BuildContext context) {
          // TODO: implement buildSuggestions
          throw UnimplementedError();
        }
      }
      

      修改樣式

      @override
      ThemeData appBarTheme(BuildContext context) {
        final ThemeData theme = Theme.of(context);
        final ColorScheme colorScheme = theme.colorScheme;
        return theme.copyWith( // 使用copyWith,適配全局主題
          appBarTheme: AppBarTheme( // AppBar樣式修改
            systemOverlayStyle: colorScheme.brightness == Brightness.dark
                ? SystemUiOverlayStyle.light
                : SystemUiOverlayStyle.dark,
            surfaceTintColor: Theme.of(context).colorScheme.surface,
            titleSpacing: 0, // textfield前面的間距
            elevation: 0, // 陰影
          ),
          inputDecorationTheme: InputDecorationTheme(
            isCollapsed: true,
            hintStyle: TextStyle( // 提示文字顏色
                color: Theme.of(ToastUtils.context).colorScheme.inversePrimary),
            filled: true,  // 填充顏色
            contentPadding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 15.w),
            fillColor: Theme.of(context).colorScheme.secondary, // 填充顏色,需要配合 filled
            enabledBorder: OutlineInputBorder( // testified 邊框
              borderRadius: BorderRadius.circular(12.r),
              borderSide: BorderSide(
                color: Theme.of(context).colorScheme.surface,
              ),
            ),
            focusedBorder: OutlineInputBorder( // testified 邊框
              borderRadius: BorderRadius.circular(12.r),
              borderSide: BorderSide(
                color: Theme.of(context).colorScheme.surface,
              ),
            ),
          ),
        );
      }
      
      @override
      TextStyle? get searchFieldStyle => TextStyle(fontSize: 14.sp); // 字體大小設置,主要是覆蓋默認樣式
      
      

      按鈕功能

      左側返回按鈕,右側就放了一個搜索文本,點擊之后顯示搜索結果

      @override
      Widget? buildLeading(BuildContext context) {
        return IconButton(
          onPressed: () {
            close(context, null);
          },
          icon: Icon(
            color: Theme.of(context).colorScheme.onSurface,
            Icons.arrow_back_ios_new,
            size: 20.r,
          ),
        );
      }
      
      @override
      List<Widget>? buildActions(BuildContext context) {
        return [
          Padding(
            padding: EdgeInsets.only(right: 15.w, left: 15.w),
            child: GestureDetector(
              onTap: () {
                showResults(context);
              },
              child: Text(
                '搜索',
                style: TextStyle(
                    color: Theme.of(context).colorScheme.primary, fontSize: 15.sp),
              ),
            ),
          )
        ];
      }
      

      搜索建議

      當 TextField 輸入變化時,就會調用buildSuggestions方法,刷新布局,因此考慮使用FlutterBuilder管理頁面和數據。

      final SearchViewModel _viewModel = SearchViewModel();
      
      @override
      Widget buildSuggestions(BuildContext context) {
        if (query.isEmpty) {
          // 這里可以展示熱門搜索等,有搜索建議時,熱門搜索會被替換成搜索建議
          return const SizedBox();
        }
        return FutureBuilder(
          future: _viewModel.getSuggest(query),
          builder: (BuildContext context, AsyncSnapshot<List<Suggest>> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              // 數據加載中
              return const Center(child: CircularProgressIndicator());
            } else if (snapshot.hasError) {
              // 數據加載錯誤
              return Center(child: Text('Error: ${snapshot.error}'));
            } else if (snapshot.hasData) {
              // 數據加載成功,展示結果
              final List<Suggest> searchResults = snapshot.data ?? [];
              return ListView.builder(
                  padding: EdgeInsets.all(15.r),
                  itemCount: searchResults.length,
                  itemBuilder: (context, index) {
                    return GestureDetector(
                      onTap: () {
                        // 更新輸入框
                        query = searchResults[index].text ?? query;
                        showResults(context);
                      },
                      child: Container(
                        padding: EdgeInsets.symmetric(vertical: 10.h),
                        decoration: BoxDecoration(
                          border: BorderDirectional(
                            bottom: BorderSide(
                              width: 0.6,
                              color: Theme.of(context).colorScheme.surfaceContainer,
                            ),
                          ),
                        ),
                        child: Text('${searchResults[index].text}'),
                      ),
                    );
                  });
            } else {
              // 數據為空
              return const Center(child: Text('No results found'));
            }
          },
        );
      }
      

      實體類代碼如下:

      class Suggest {
        Suggest({
          this.id,
          this.url,
          this.text,
          this.isHot,
          this.hotLevel,
        });
      
        Suggest.fromJson(dynamic json) {
          id = json['id'];
          url = json['url'];
          text = json['text'];
          isHot = json['is_hot'];
          hotLevel = json['hot_level'];
        }
      
        String? id;
        String? url;
        String? text;
        bool? isHot;
        int? hotLevel;
      }
      

      ViewModel代碼如下:

      class SearchViewModel {
        Future<List<Suggest>> getSuggest(String keyword) async {
          if (keyword.isEmpty) {
            return [];
          }
          return await JsonApi.instance().fetchSuggestV3(keyword);
        }
      }
      

      搜索結果

      我們需要搜索結果頁面支持加載更多,這里用到了 SmartRefrsh 組件

      flutter pub add pull_to_refresh
      

      buildResults方法是通過調用showResults(context);方法刷新頁面,因此為了方便數據動態變化,新建search_result_page.dart頁面

      import 'package:e_book_clone/components/book_tile/book_tile_vertical/my_book_tile_vertical_item.dart';
      import 'package:e_book_clone/components/book_tile/book_tile_vertical/my_book_tile_vertical_item_skeleton.dart';
      import 'package:e_book_clone/components/my_smart_refresh.dart';
      import 'package:e_book_clone/models/book.dart';
      import 'package:e_book_clone/models/types.dart';
      import 'package:e_book_clone/pages/search/search_vm.dart';
      import 'package:e_book_clone/utils/navigator_utils.dart';
      import 'package:flutter/material.dart';
      import 'package:flutter_screenutil/flutter_screenutil.dart';
      import 'package:provider/provider.dart';
      import 'package:pull_to_refresh/pull_to_refresh.dart';
      
      class SearchResultPage extends StatefulWidget {
        final String query; // 請求參數
        const SearchResultPage({super.key, required this.query});
      
        @override
        State<SearchResultPage> createState() => _SearchResultPageState();
      }
      
      class _SearchResultPageState extends State<SearchResultPage> {
        final RefreshController _refreshController = RefreshController();
        final SearchViewModel _viewModel = SearchViewModel();
        void loadOrRefresh(bool loadMore) {
          _viewModel.getResults(widget.query, loadMore).then((_) {
            if (loadMore) {
              _refreshController.loadComplete();
            } else {
              _refreshController.refreshCompleted();
            }
          });
        }
      
        @override
        void initState() {
          super.initState();
          loadOrRefresh(false);
        }
      
        @override
        void dispose() {
          _viewModel.isDispose = true;
          _refreshController.dispose();
          super.dispose();
        }
      
        @override
        Widget build(BuildContext context) {
          return ChangeNotifierProvider<SearchViewModel>.value(
            value:  _viewModel,
            builder: (context, child) {
              return Consumer<SearchViewModel>(
                builder: (context, vm, child) {
                  List<Book>? searchResult = vm.searchResult;
                  // 下拉刷新和上拉加載組件
                  return MySmartRefresh(
                    enablePullDown: false,
                    onLoading: () {
                      loadOrRefresh(true);
                    },
                    controller: _refreshController,
                    child: ListView.builder(
                      padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 15.h),
                      itemCount: searchResult?.length ?? 10,
                      itemBuilder: (context, index) {
                        if (searchResult == null) {
                          // 骨架屏 
                          return MyBookTileVerticalItemSkeleton(
                              width: 80.w, height: 120.h);
                        }
                        // 結果渲染組件
                        return MyBookTileVerticalItem(
                          book: searchResult[index],
                          width: 80.w,
                          height: 120.h,
                          onTap: (id) {
                            NavigatorUtils.nav2Detail(
                                context, DetailPageType.ebook, searchResult[index]);
                          },
                        );
                      },
                    ),
                  );
                },
              );
            },
          );
        }
      }
      

      MySmartRefresh組件代碼如下,主要是對SmartRefresher做了進一步的封裝

      import 'package:flutter/material.dart';
      import 'package:pull_to_refresh/pull_to_refresh.dart';
      
      class MySmartRefresh extends StatelessWidget {
        // 啟用下拉
        final bool? enablePullDown;
      
        // 啟用上拉
        final bool? enablePullUp;
      
        // 頭布局
        final Widget? header;
      
        // 尾布局
        final Widget? footer;
      
        // 刷新事件
        final VoidCallback? onRefresh;
      
        // 加載事件
        final VoidCallback? onLoading;
      
        // 刷新組件控制器
        final RefreshController controller;
      
        final ScrollController? scrollController;
      
        // 被刷新的子組件
        final Widget child;
      
        const MySmartRefresh({
          super.key,
          this.enablePullDown,
          this.enablePullUp,
          this.header,
          this.footer,
          this.onLoading,
          this.onRefresh,
          required this.controller,
          required this.child,
          this.scrollController,
        });
      
        @override
        Widget build(BuildContext context) {
          return _refreshView();
        }
      
        Widget _refreshView() {
          return SmartRefresher(
            scrollController: scrollController,
            controller: controller,
            enablePullDown: enablePullDown ?? true,
            enablePullUp: enablePullUp ?? true,
            header: header ?? const ClassicHeader(),
            footer: footer ?? const ClassicFooter(),
            onRefresh: onRefresh,
            onLoading: onLoading,
            child: child,
          );
        }
      }
      

      SearchViewModel 代碼如下:

      import 'package:e_book_clone/http/spider/json_api.dart';
      import 'package:e_book_clone/models/book.dart';
      import 'package:e_book_clone/models/query_param.dart';
      import 'package:e_book_clone/models/suggest.dart';
      import 'package:flutter/material.dart';
      
      class SearchViewModel extends ChangeNotifier {
        int _currPage = 2;
        bool isDispose = false;
        List<Book>? _searchResult;
      
        List<Book>? get searchResult => _searchResult;
      
        Future<List<Suggest>> getSuggest(String keyword) async {
          if (keyword.isEmpty) {
            return [];
          }
          return await JsonApi.instance().fetchSuggestV3(keyword);
        }
      
        Future getResults(String keyword, bool loadMore, {VoidCallback? callback}) async {
          if (loadMore) {
            _currPage++;
          } else {
            _currPage = 1;
            _searchResult?.clear();
          }
      
          // 請求參數
          SearchParam param = SearchParam(
            page: _currPage,
            rootKind: null,
            q: keyword,
            sort: "defalut",
            query: SearchParam.ebookSearch,
          );
          // 請求結果
          List<Book> res = await JsonApi.instance().fetchEbookSearch(param);
      	
          // 加載更多,使用addAll
          if (_searchResult == null) {
            _searchResult = res;
          } else {
            _searchResult!.addAll(res);
          }
      
          if (res.isEmpty && _currPage > 0) {
            _currPage--;
          }
          // 防止Provider被銷毀,數據延遲請求去通知報錯
          if (isDispose) return;
          notifyListeners();
        }
      }
      

      buildResults方法如下:

      @override
      Widget buildResults(BuildContext context) {
        if (query.isEmpty) {
          return const SizedBox();
        }
        return SearchResultPage(query: query);
      }
      

      顯示搜索界面

      注意調用的是我們自己拷貝修改的MySearchDelegate中的方法

      onTap: () {
        showMySearch(context: context, delegate: SearchPage());
      },
      

      更多內容見

      posted @ 2024-06-19 23:36  sw-code  閱讀(930)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 免费久久人人爽人人爽AV| 老熟女高潮一区二区三区| 公喝错春药让我高潮| 欧美在线观看www| 巨胸美乳无码人妻视频漫画| 国产精品欧美亚洲韩国日本久久| 亚洲av无码专区在线亚| 国产精品一区二区三区色| P尤物久久99国产综合精品| 精品一区二区不卡免费| 亚洲午夜av一区二区| 四虎影视一区二区精品| 免费网站看sm调教视频| аⅴ天堂国产最新版在线中文| 欧洲亚洲国内老熟女超碰| 亚洲av无码专区在线厂| 日韩精品国产中文字幕| 亚洲av一本二本三本| 四虎影视永久无码精品 | 欧美黑人粗暴多交高潮水最多| 欧美偷窥清纯综合图区| 亚洲一精品一区二区三区| 亚洲国产日韩欧美一区二区三区| 国产精品一区中文字幕| 丰满人妻一区二区三区无码AV| 国产高清自产拍AV在线| 亚洲一区二区三区在线观看精品中文| 国产自在自线午夜精品| 免费看黄色片| 欧美成人午夜在线观看视频| 精品国产伦理国产无遮挡| 亚洲一区无码精品色 | 国产精品日韩中文字幕熟女| 亚洲暴爽av天天爽日日碰| 国产精品一区二区色综合| 精品久久久久久无码人妻蜜桃| 亚洲国产高清第一第二区| 亚洲成av人片在www鸭子| 梧州市| 国产精品亚洲二区在线看| 亚洲国产欧美一区二区好看电影 |