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

Flutter点击搜索时隐藏Tab名称实现方案咨询

Flutter问题:点击搜索框时隐藏Tab名称

我是Flutter新手,想实现点击搜索框时隐藏Tab名称的效果——未点击搜索框时正常显示Tab栏;点击搜索框后Tab栏隐藏,搜索框占据原本Tab栏的空间。但当前自定义TabbedCard组件的实现效果不符合预期,代码如下:

class TabbedCard extends StatefulWidget {
  final List<Tab> tabs;
  final List<Widget> children;
  final bool showDeleteBtn;
  final bool showPrimaryBtn;

  const TabbedCard(
      {Key? key,
      required this.tabs,
      required this.children,
      this.showDeleteBtn = false,
      this.showPrimaryBtn = true})
      : super(key: key);

  @override
  State<TabbedCard> createState() => _TabbedCardState();
}

class _TabbedCardState extends State<TabbedCard> with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    double _scrHeight = MediaQuery.of(context).size.height - 150;

    // String searchText = "";

    TextEditingController textController = new TextEditingController();

    TabController _controller =
        TabController(length: widget.tabs.length, vsync: this);

    TabBar _tabBar = new TabBar(
      isScrollable: true,
      labelColor: kPrimaryColor,
      unselectedLabelColor: kTextGray,
      controller: _controller,
      tabs: widget.tabs,
    );

    return Container(
      constraints: BoxConstraints(minHeight: _scrHeight),
      child: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              padding: const EdgeInsets.symmetric(
                vertical: 1,
                horizontal: 1,
              ),
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.white,
                ),
                color: Colors.white,
                boxShadow: [
                  BoxShadow(
                    color: Colors.white.withOpacity(0.5),
                    spreadRadius: -4,
                    blurRadius: 35,
                    offset: const Offset(0, 9), // changes position of shadow
                  ),
                ],
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Align(
                      alignment: Alignment.topLeft,
                      child: Row(
                        children: [
                          AnimSearchBar(
                            width: 400,
                            textController: textController,
                            onSuffixTap: (value) {
                              setState(() {});
                            },
                          ),
                          const SizedBox(width: 10),
                          _tabBar,
                          const SizedBox(width: 10),
                        ],
                      )),
                  const SizedBox(height: 10),
                  Container(
                    height: 440,
                    child: TabBarView(
                      controller: _controller,
                      children: widget.children,
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 10),
          ],
        ),
      ),
    );
  }
}

当前实现的核心问题:

  • 控制器(TextEditingController、TabController)在build方法内声明,每次组件重建都会重新初始化,导致状态丢失
  • 未监听搜索框的焦点状态,无法触发Tab栏的显示/隐藏逻辑

修复方案

通过添加焦点监听、提取状态变量,实现Tab栏的动态显示/隐藏:

class TabbedCard extends StatefulWidget {
  final List<Tab> tabs;
  final List<Widget> children;
  final bool showDeleteBtn;
  final bool showPrimaryBtn;

  const TabbedCard(
      {Key? key,
      required this.tabs,
      required this.children,
      this.showDeleteBtn = false,
      this.showPrimaryBtn = true})
      : super(key: key);

  @override
  State<TabbedCard> createState() => _TabbedCardState();
}

class _TabbedCardState extends State<TabbedCard> with TickerProviderStateMixin {
  // 提取为成员变量,避免build重建导致状态丢失
  late final TextEditingController _textController;
  late final TabController _tabController;
  late final FocusNode _searchFocusNode;
  // 跟踪搜索框是否获得焦点
  bool _isSearchFocused = false;

  @override
  void initState() {
    super.initState();
    _textController = TextEditingController();
    _tabController = TabController(length: widget.tabs.length, vsync: this);
    _searchFocusNode = FocusNode();
    // 监听搜索框焦点变化,更新状态
    _searchFocusNode.addListener(() {
      setState(() {
        _isSearchFocused = _searchFocusNode.hasFocus;
      });
    });
  }

  @override
  void dispose() {
    // 释放资源,避免内存泄漏
    _textController.dispose();
    _tabController.dispose();
    _searchFocusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    double _scrHeight = MediaQuery.of(context).size.height - 150;

    TabBar _tabBar = TabBar(
      isScrollable: true,
      labelColor: kPrimaryColor,
      unselectedLabelColor: kTextGray,
      controller: _tabController,
      tabs: widget.tabs,
    );

    return Container(
      constraints: BoxConstraints(minHeight: _scrHeight),
      child: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 1),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.white),
                color: Colors.white,
                boxShadow: [
                  BoxShadow(
                    color: Colors.white.withOpacity(0.5),
                    spreadRadius: -4,
                    blurRadius: 35,
                    offset: const Offset(0, 9),
                  ),
                ],
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Align(
                    alignment: Alignment.topLeft,
                    child: Row(
                      children: [
                        // 根据焦点状态动态调整搜索框宽度
                        AnimSearchBar(
                          width: _isSearchFocused 
                              ? MediaQuery.of(context).size.width - 40 
                              : 400,
                          textController: _textController,
                          focusNode: _searchFocusNode,
                          onSuffixTap: (value) {
                            setState(() {});
                          },
                        ),
                        const SizedBox(width: 10),
                        // 搜索框聚焦时隐藏Tab栏
                        if (!_isSearchFocused) _tabBar,
                        const SizedBox(width: 10),
                      ],
                    ),
                  ),
                  const SizedBox(height: 10),
                  Container(
                    height: 440,
                    child: TabBarView(
                      controller: _tabController,
                      children: widget.children,
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 10),
          ],
        ),
      ),
    );
  }
}

关键修改说明:

  1. 将控制器和FocusNode移到initState中初始化,在dispose中释放资源,避免内存泄漏和状态丢失
  2. 通过FocusNode监听搜索框焦点变化,更新_isSearchFocused状态
  3. 根据焦点状态动态控制TabBar的显示/隐藏,同时调整搜索框宽度,让搜索框在聚焦时占满可用空间
  4. 给AnimSearchBar传入focusNode,确保焦点监听生效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 11:18:43