之前曾针对 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 列表,则逻辑工作正常,并且复选框会根据我的需要进行更新。然而,一旦它们成为菜单的一部分,它就不再正常工作了。
感谢您对此提供的任何帮助! 托尼
您需要在
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(', ')}'),
],
),
),
);
}
}