You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 11:35:20