Flutter中如何实现SearchAnchor建议列表自适应内容尺寸?
调整Flutter SearchAnchor建议列表以适配内容大小
我正在尝试调整Flutter中SearchAnchor的建议列表视图,使其适配自身内容。使用了Flutter官方文档的基础代码,但由于需要调用API获取数据,无法预先确定建议数量。目前代码可正常运行,但建议列表尺寸过大,即使填充内容后仍有大量空白。希望初始显示较短的空白列表,API调用完成后让列表自适应内容大小。
现有代码
import 'package:flutter/material.dart'; /// Flutter code sample for [SearchAnchor]. const Duration fakeAPIDuration = Duration(seconds: 1); void main() => runApp(const SearchAnchorAsyncExampleApp()); class SearchAnchorAsyncExampleApp extends StatelessWidget { const SearchAnchorAsyncExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('SearchAnchor - async'), ), body: const Center( child: _AsyncSearchAnchor(), ), ), ); } } class _AsyncSearchAnchor extends StatefulWidget { const _AsyncSearchAnchor(); @override State<_AsyncSearchAnchor> createState() => _AsyncSearchAnchorState(); } class _AsyncSearchAnchorState extends State<_AsyncSearchAnchor> { // The query currently being searched for. If null, there is no pending // request. String? _searchingWithQuery; // The most recent options received from the API. late Iterable<Widget> _lastOptions = <Widget>[]; @override Widget build(BuildContext context) { return SearchAnchor( builder: (BuildContext context, SearchController controller) { return IconButton( icon: const Icon(Icons.search), onPressed: () { controller.openView(); }, ); }, suggestionsBuilder: (BuildContext context, SearchController controller) async { _searchingWithQuery = controller.text; final List<String> options = (await _FakeAPI.search(_searchingWithQuery!)).toList(); // If another search happened after this one, throw away these options. // Use the previous options instead and wait for the newer request to // finish. if (_searchingWithQuery != controller.text) { return _lastOptions; } _lastOptions = List<ListTile>.generate(options.length, (int index) { final String item = options[index]; return ListTile( title: Text(item), ); }); return _lastOptions; }); } } // Mimics a remote API. class _FakeAPI { static const List<String> _kOptions = <String>[ 'aardvark', 'bobcat', 'chameleon', ]; // Searches the options, but injects a fake "network" delay. static Future<Iterable<String>> search(String query) async { await Future<void>.delayed(fakeAPIDuration); // Fake 1 second delay. if (query == '') { return const Iterable<String>.empty(); } return _kOptions.where((String option) { return option.contains(query.toLowerCase()); }); } }
初始与填充后状态


解决方案
要让SearchAnchor的建议列表自适应内容,核心是控制列表的高度约束,同时让列表根据内容动态收缩/扩展,以下是两种可行方法:
方法一:在suggestionsBuilder中添加约束容器
修改suggestionsBuilder的返回逻辑,用ConstrainedBox结合shrinkWrap属性限制列表高度:
suggestionsBuilder: (BuildContext context, SearchController controller) async { _searchingWithQuery = controller.text; final List<String> options = (await _FakeAPI.search(_searchingWithQuery!)).toList(); if (_searchingWithQuery != controller.text) { return _lastOptions; } _lastOptions = List<ListTile>.generate(options.length, (int index) { final String item = options[index]; return ListTile( title: Text(item), ); }); // 包装约束容器,实现自适应高度 return [ ConstrainedBox( constraints: BoxConstraints( minHeight: 80, // 初始空白状态的最小高度 maxHeight: 300, // 内容过多时的最大高度上限 ), child: ListView.builder( shrinkWrap: true, padding: EdgeInsets.zero, itemCount: _lastOptions.length, itemBuilder: (context, index) => _lastOptions.elementAt(index), ), ) ]; }
方法二:用viewBuilder自定义弹出视图
如果需要更灵活的控制,可以通过viewBuilder完全自定义弹出层的样式和尺寸:
return SearchAnchor( viewBuilder: (context, controller, suggestions) { return ConstrainedBox( constraints: BoxConstraints( maxHeight: suggestions.isNotEmpty ? MediaQuery.of(context).size.height * 0.4 : 80, minWidth: MediaQuery.of(context).size.width * 0.8, ), child: Material( elevation: 4.0, child: ListView.builder( shrinkWrap: true, itemCount: suggestions.length, itemBuilder: (context, index) => suggestions.elementAt(index), ), ), ); }, builder: (BuildContext context, SearchController controller) { return IconButton( icon: const Icon(Icons.search), onPressed: () { controller.openView(); }, ); }, suggestionsBuilder: (BuildContext context, SearchController controller) async { // 保持原有的异步请求逻辑 _searchingWithQuery = controller.text; final List<String> options = (await _FakeAPI.search(_searchingWithQuery!)).toList(); if (_searchingWithQuery != controller.text) { return _lastOptions; } _lastOptions = List<ListTile>.generate(options.length, (int index) { final String item = options[index]; return ListTile( title: Text(item), ); }); return _lastOptions; }, );
关键说明
shrinkWrap: true:让ListView仅占用内容所需空间,而非默认填充父容器ConstrainedBox:通过minHeight和maxHeight控制列表的高度范围,兼顾初始空白状态和内容过多的情况viewBuilder:提供完全自定义弹出层的能力,适合需要调整弹出层位置、样式等更复杂的场景
内容的提问来源于stack exchange,提问作者JPFrancoia
相关产品推荐
相关产品推荐

