如何在选择一个复选框时选择 Flutter PopupMenu 中的所有复选框?

问题描述 投票:0回答:1

之前曾针对 Flutter 提出过类似的问题,请参阅问题。然而没有给出有效的答案,所以可能值得重新打开。

这是一个完整的代码示例。

import 'package:flutter/material.dart';

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

const cities = <String>{
  'Atlanta',
  'Baltimore',
  'Boston',
  'Chicago',
  'Denver',
  'Houston',
  'Los Angeles',
  'Philadelphia',
  'San Francisco',
  'Washington, DC',
};

enum SelectionState {
  all('(All)'),
  none('(None)'),
  some('(Some)');

  const SelectionState(this._value);
  final String _value;

  @override
  String toString() => _value;
}

class SelectionModel {
  SelectionModel({required this.selection, required this.choices});

  late final Set<String> selection;
  final Set<String> choices;

  SelectionState get selectionState {
    if (selection.isEmpty) return SelectionState.none;
    if (choices.difference(selection).isNotEmpty) {
      return SelectionState.some;
    } else {
      return SelectionState.all;
    }
  }

  SelectionModel add(String value) {
    if (value == '(All)') {
      return SelectionModel(selection: {...choices}, choices: choices);
    } else {
      return SelectionModel(selection: selection..add(value), choices: choices);
    }
  }

  SelectionModel remove(String value) {
    if (value == '(All)') {
      return SelectionModel(selection: <String>{}, choices: choices);
    } else {
      selection.remove(value);
      return SelectionModel(selection: selection, choices: choices);
    }
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dropdown with Select (All)',
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  SelectionModel model =
      SelectionModel(selection: {...cities}, choices: {...cities});

  List<PopupMenuItem<String>> getCheckboxList() {
    var out = <PopupMenuItem<String>>[];
    out.add(PopupMenuItem<String>(
        padding: EdgeInsets.zero,
        value: '(All)',
        child: StatefulBuilder(builder: (context, setState) {
          return CheckboxListTile(
            value: model.selectionState == SelectionState.all,
            controlAffinity: ListTileControlAffinity.leading,
            title: const Text('(All)'),
            onChanged: (bool? checked) {
              setState(() {
                if (checked!) {
                  model = model.add('(All)');
                } else {
                  model = model.remove('(All)');
                }
              });
            },
          );
        })));

    for (final value in model.choices) {
      out.add(PopupMenuItem<String>(
          padding: EdgeInsets.zero,
          value: value,
          child: StatefulBuilder(builder: (context, setState) {
            return CheckboxListTile(
              value: model.selection.contains(value),
              controlAffinity: ListTileControlAffinity.leading,
              title: Text(value),
              onChanged: (bool? checked) {
                setState(() {
                  if (checked!) {
                    model = model.add(value);
                  } else {
                    model = model.remove(value);
                  }
                });
              },
            );
          })));
    }
    return out;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: Container(
                width: 250,
                color: Colors.orangeAccent,
                child: PopupMenuButton<String>(
                  constraints: const BoxConstraints(maxHeight: 400),
                  position: PopupMenuPosition.under,
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Row(
                      children: [
                        Text(model.selectionState.toString()),
                        const Spacer(),
                        const Icon(Icons.keyboard_arrow_down_outlined),
                      ],
                    ),
                  ),
                  itemBuilder: (context) {
                    return getCheckboxList();
                  },
                  onCanceled: () {
                    setState(() {
                      model = SelectionModel(
                          selection: model.selection, choices: model.choices);
                    });
                  },
                ),
              ),
            ),
            const Spacer(),
            Text('Selected cities: ${model.selection.join(', ')}'),
          ],
        ),
      ),
    );
  }
}

参见屏幕截图。

如果我点击各个城市,一切都很好。如果我单击(全部)复选框,我希望所有复选框都变为 false(除非关闭菜单,否则不会发生这种情况。)

我该怎么做?如果我在主应用程序中只有一个 CheckboxListTiles 列表,则逻辑工作正常,并且复选框会根据我的需要进行更新。然而,一旦它们成为菜单的一部分,它就不再正常工作了。

感谢您对此提供的任何帮助! 托尼

flutter checkbox dropdown popupmenu
1个回答
0
投票

您需要在

all
和其他项目之间建立连接。使用单独的
StateFulBuilder
只是更新不同的部分更新。我正在使用 ValueNotifier 扩展这种方法。

  SelectionModel selectAll() {
    return SelectionModel(selection: {...choices}, choices: choices);
  }

  SelectionModel selectNone() {
    return SelectionModel(selection: <String>{}, choices: choices);
  }

还有 ValueNotifier。

  ValueNotifier<SelectionModel> model = ValueNotifier(
      SelectionModel(selection: {...cities}, choices: {...cities}));
///there are N things can be improved, 
void main() {
  runApp(const MyApp());
}

const cities = <String>{
  'Atlanta',
  'Baltimore',
  'Boston',
  'Chicago',
  'Denver',
  'Houston',
  'Los Angeles',
  'Philadelphia',
  'San Francisco',
  'Washington, DC',
};

enum SelectionState {
  all('(All)'),
  none('(None)'),
  some('(Some)');

  const SelectionState(this._value);
  final String _value;

  @override
  String toString() => _value;
}

class SelectionModel {
  SelectionModel({required this.selection, required this.choices});

  late final Set<String> selection;
  final Set<String> choices;

  SelectionState get selectionState {
    if (selection.isEmpty) return SelectionState.none;
    if (choices.difference(selection).isNotEmpty) {
      return SelectionState.some;
    } else {
      return SelectionState.all;
    }
  }

  SelectionModel add(String value) {
    if (value == '(All)') {
      return SelectionModel(selection: {...choices}, choices: choices);
    } else {
      return SelectionModel(selection: selection..add(value), choices: choices);
    }
  }

  SelectionModel remove(String value) {
    if (value == '(All)') {
      return SelectionModel(selection: <String>{}, choices: choices);
    } else {
      selection.remove(value);
      return SelectionModel(selection: selection, choices: choices);
    }
  }

  SelectionModel selectAll() {
    return SelectionModel(selection: {...choices}, choices: choices);
  }

  SelectionModel selectNone() {
    return SelectionModel(selection: <String>{}, choices: choices);
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dropdown with Select (All)',
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ValueNotifier<SelectionModel> model = ValueNotifier(
      SelectionModel(selection: {...cities}, choices: {...cities}));

  List<PopupMenuItem<String>> getCheckboxList(setStateSB) {
    var out = <PopupMenuItem<String>>[];
    out.add(PopupMenuItem<String>(
        padding: EdgeInsets.zero,
        value: '(All)',
        child: ValueListenableBuilder(
          valueListenable: model,
          builder: (context, value, child) => CheckboxListTile(
              value: model.value.selectionState == SelectionState.all,
              controlAffinity: ListTileControlAffinity.leading,
              title: const Text('(All)'),
              onChanged: (bool? checked) {
                if (checked == true) {
                  model.value = model.value.selectAll();
                } else {
                  model.value = model.value.selectNone();
                }
                setStateSB(() {});
              }),
        )));

    for (final value in model.value.choices) {
      out.add(PopupMenuItem<String>(
          padding: EdgeInsets.zero,
          value: value,
          child: ValueListenableBuilder(
            valueListenable: model,
            builder: (context, _, child) => CheckboxListTile(
              // just using your approach
              value: model.value.selection.contains(value),
              controlAffinity: ListTileControlAffinity.leading,
              title: Text(value),
              onChanged: (bool? checked) {
                setStateSB(() {
                  if (checked!) {
                    model.value = model.value.add(value);
                  } else {
                    model.value = model.value.remove(value);
                  }
                });
              },
            ),
          )));
    }
    return out;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: Container(
                width: 250,
                color: Colors.orangeAccent,
                child: StatefulBuilder(
                  builder: (context, setStateSB) => PopupMenuButton<String>(
                    constraints: const BoxConstraints(maxHeight: 400),
                    position: PopupMenuPosition.under,
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Row(
                        children: [
                          Text(model.value.selectionState.toString()),
                          const Spacer(),
                          const Icon(Icons.keyboard_arrow_down_outlined),
                        ],
                      ),
                    ),
                    itemBuilder: (context) {
                      return getCheckboxList(setStateSB);
                    },
                    onCanceled: () {
                      setState(() {
                        model.value = SelectionModel(
                            selection: model.value.selection,
                            choices: model.value.choices);
                      });
                    },
                  ),
                ),
              ),
            ),
            const Spacer(),
            Text('Selected cities: ${model.value.selection.join(', ')}'),
          ],
        ),
      ),
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.