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

Flutter中DropdownSearch展开时隐藏AppBar返回按钮的问题

解决方案:DropdownSearch展开时隐藏AppBar返回按钮

问题原因

你之前使用FocusNode的方案失效,核心问题有两个:

  1. 每次执行_buildDropdownSearch都会重新创建FocusNode,导致监听逻辑重复绑定、状态混乱;
  2. 你监听的是搜索框的焦点变化,而非下拉弹窗的实际展开/关闭状态,两者并不完全同步。

正确实现思路

DropdownSearch的PopupProps提供了专门监听弹窗展开和关闭的回调:onPopupOpened和onPopupClosed,用这两个回调控制返回按钮的显示/隐藏是最准确的方案。

修改步骤与代码示例

1. 优化Controller状态管理(可选但更符合GetX规范)

将showBackButton改为Rx变量,无需手动调用update(),GetX会自动响应状态变化:

class QuestionnaireController extends GetxController
    with GetSingleTickerProviderStateMixin {
  RxList<String> choices = <String>[].obs;
  RxBool showBackButton = true.obs; // 改为Rx布尔变量
}

如果不想修改变量类型,保持原bool showBackButton = true也可以,但需要在修改状态后手动调用controller.update()。

2. 配置DropdownSearch的弹窗回调

在PopupPropsMultiSelection.bottomSheet中添加弹窗展开/关闭的回调,直接修改Controller状态:

popupProps: PopupPropsMultiSelection.bottomSheet(
  // 保留原有配置...
  onPopupOpened: () {
    // 弹窗展开时隐藏返回按钮
    controller.showBackButton.value = false;
    // 若使用普通bool变量,替换为:
    // controller.showBackButton = false;
    // controller.update();
  },
  onPopupClosed: () {
    // 弹窗关闭时显示返回按钮
    controller.showBackButton.value = true;
    // 若使用普通bool变量,替换为:
    // controller.showBackButton = true;
    // controller.update();
  },
  // 保留原有配置...
),

3. 移除无用的FocusNode代码

删除_buildDropdownSearch中创建FocusNode、添加监听器的逻辑,以及搜索框的focusNode: _dropdownFocusNode配置。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:get/get.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'MultiSelectDropDown',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'MultiSelectDropDown'),
      initialBinding: QuestionnaireBinding(),
    );
  }
}

class QuestionnaireBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<QuestionnaireController>(() => QuestionnaireController());
  }
}

class QuestionnaireController extends GetxController
    with GetSingleTickerProviderStateMixin {
  RxList<String> choices = <String>[].obs;
  RxBool showBackButton = true.obs;
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final dropdownSearchTheme = ThemeData(
      colorScheme: const ColorScheme(
    primary: Color(0xFF485946),
    onPrimary: Colors.white,
    secondary: Colors.black,
    onSecondary: Colors.deepOrange,
    error: Colors.transparent,
    onError: Colors.transparent,
    background: Colors.transparent,
    onBackground: Colors.black,
    brightness: Brightness.light,
    onSurface: Colors.black,
    surface: Colors.white,
  ));

  @override
  Widget build(BuildContext context) {
    return Obx(() {
      final controller = Get.find<QuestionnaireController>();
      return Scaffold(
        appBar: AppBar(
          leading: controller.showBackButton.value ? BackButton() : null,
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
        ),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: [
                const Text(
                  'Let\'s start choosing',
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 30),
                Theme(
                  data: dropdownSearchTheme,
                  child: _buildDropdownSearch(
                    context,
                    controller.choices,
                    dropdownCustomBuilder: _customChoice,
                  ),
                ),
              ],
            ),
          ),
        ),
      );
    });
  }

  Widget _buildDropdownSearch(
      BuildContext context, RxList<String> selectedChoice,
      {Widget Function(BuildContext, List<String>)? dropdownCustomBuilder}) {
    final controller = Get.find<QuestionnaireController>();

    return DropdownSearch<String>.multiSelection(
      selectedItems: selectedChoice,
      items: [
        "choice1", "choice2", "choice3", "choice4", "choice5",
        "choice6", "choice7", "choice8", "choice9", "choice10", "choice11"
      ],
      dropdownDecoratorProps: DropDownDecoratorProps(
          dropdownSearchDecoration: InputDecoration(
        hintText: 'pickChoice',
        hintStyle: TextStyle(color: Colors.black),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(8.0),
          borderSide: BorderSide(color: Colors.grey),
        ),
      )),
      dropdownBuilder: dropdownCustomBuilder,
      popupProps: PopupPropsMultiSelection.bottomSheet(
        searchFieldProps: TextFieldProps(
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
          decoration: InputDecoration(
            hintText: 'Search for choice',
            hintStyle: TextStyle(color: Colors.black),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(8.0),
              borderSide: BorderSide(color: Colors.grey),
            ),
            suffixIcon: IconButton(
              icon: Icon(Icons.clear),
              onPressed: () => controller.choices.clear(),
            ),
          ),
          style: TextStyle(color: Colors.black),
          autofocus: true,
        ),
        showSearchBox: true,
        showSelectedItems: true,
        isFilterOnline: false,
        onItemAdded: (selectedItems, addedItem) => selectedChoice.add(addedItem),
        onItemRemoved: (selectedItems, removedItem) => selectedChoice.remove(removedItem),
        onPopupOpened: () => controller.showBackButton.value = false,
        onPopupClosed: () => controller.showBackButton.value = true,
      ),
    );
  }

  Widget _customChoice(BuildContext context, List<String> selectedItems) {
    final controller = Get.find<QuestionnaireController>();
    return Obx(() {
      if (selectedItems.isEmpty) return Text("Pick a choice");
      return Wrap(
        spacing: 2,
        runSpacing: -10,
        children: selectedItems.map((e) {
          return Chip(
            backgroundColor: Colors.lightGreen,
            label: Text(e.toString().trim()),
            onDeleted: () {
              selectedItems.remove(e.toString().trim());
              controller.choices.remove(e.toString().trim());
            },
          );
        }).toList(),
      );
    });
  }
}

说明

  • onPopupOpened和onPopupClosed能精准监听弹窗的展开/关闭状态,比FocusNode方案更可靠;
  • 使用Rx变量+Obx可以自动响应状态变化,无需手动调用update(),代码更简洁;
  • 若坚持使用普通bool变量,只需在回调中修改状态后调用controller.update()即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 03:27:02