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

Flutter中如何将TextFormField自定义为多选搜索组件?

实现带多选标签的搜索输入框方案

针对你需要的「选择搜索结果后在输入框内添加标签、支持多次选择」的需求,这里提供一个Flutter端的可行实现方案:

核心实现思路

  1. 用Wrap组件组合已选标签(Chip)和输入框,实现标签换行布局
  2. 维护一个已选条目列表,用于渲染标签和控制输入框状态
  3. 监听输入框文本变化,实时触发搜索并展示结果列表
  4. 点击搜索结果时将条目加入已选列表,同时清空输入框
  5. 支持点击标签的删除按钮移除对应条目

完整代码示例

import 'package:flutter/material.dart';

void main() => runApp(const TagSearchApp());

class TagSearchApp extends StatelessWidget {
  const TagSearchApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("标签搜索示例")),
        body: const TagSearchField(),
      ),
    );
  }
}

class TagSearchField extends StatefulWidget {
  const TagSearchField({super.key});

  @override
  State<TagSearchField> createState() => _TagSearchFieldState();
}

class _TagSearchFieldState extends State<TagSearchField> {
  // 已选条目列表
  final List<String> _selectedItems = [];
  // 搜索输入文本控制器
  final TextEditingController _searchController = TextEditingController();
  // 当前搜索结果
  List<String> _searchResults = [];
  // 模拟全量搜索关键词池
  final List<String> _allKeywords = ["Item 0", "Item 1", "Item 2", "Item 3", "Item 4", "Item 5"];

  @override
  void initState() {
    super.initState();
    // 监听输入变化触发搜索
    _searchController.addListener(_performSearch);
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  // 执行模糊搜索逻辑
  void _performSearch() {
    final query = _searchController.text.trim();
    if (query.isEmpty) {
      setState(() => _searchResults = []);
      return;
    }
    setState(() {
      _searchResults = _allKeywords.where((item) => item.contains(query)).toList();
    });
  }

  // 添加选中条目到标签列表
  void _addSelectedItem(String item) {
    if (!_selectedItems.contains(item)) {
      setState(() {
        _selectedItems.add(item);
        _searchController.clear();
        _searchResults = [];
      });
    }
  }

  // 删除指定标签
  void _removeSelectedItem(String item) {
    setState(() => _selectedItems.remove(item));
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 自定义输入区域:标签 + 输入框
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
              border: Border.all(color: Colors.grey),
              borderRadius: BorderRadius.circular(8),
            ),
            child: Wrap(
              spacing: 8,
              runSpacing: 4,
              children: [
                // 渲染已选标签
                ..._selectedItems.map((item) => Chip(
                      label: Text(item),
                      onDeleted: () => _removeSelectedItem(item),
                    )),
                // 搜索输入框
                SizedBox(
                  width: 120,
                  child: TextField(
                    controller: _searchController,
                    decoration: const InputDecoration(
                      border: InputBorder.none,
                      hintText: "搜索...",
                    ),
                  ),
                ),
              ],
            ),
          ),
          // 搜索结果下拉列表
          if (_searchResults.isNotEmpty)
            Container(
              margin: const EdgeInsets.only(top: 8),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey.shade300),
                borderRadius: BorderRadius.circular(8),
              ),
              child: ListView.builder(
                shrinkWrap: true,
                itemCount: _searchResults.length,
                itemBuilder: (context, index) {
                  final item = _searchResults[index];
                  return ListTile(
                    title: Text(item),
                    onTap: () => _addSelectedItem(item),
                  );
                },
              ),
            ),
        ],
      ),
    );
  }
}

关键细节说明

  • 标签布局:Wrap组件实现标签自动换行,避免输入区域横向溢出
  • 搜索交互:通过TextEditingController监听输入内容,实时过滤搜索结果
  • 标签管理:点击搜索结果自动添加标签,点击标签删除按钮移除条目,同时做了重复添加的判断
  • 样式适配:自定义输入区域的边框样式,可根据需求调整颜色、圆角、间距等参数匹配你的UI设计

内容的提问来源于stack exchange,提问作者Quang Anh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 15:25:19