GOOGLE ADS

mercredi 13 avril 2022

Tuile à double expansion

Je veux écrire des filtres avec des catégories et des sous-catégories qui peuvent se présenter sous la forme d'une liste déroulante. J'ai écrit ceci avec un ExpansionTile et mis une case à cocher dans le titre de l'ExpansionTile. Et j'ai rencontré le problème que la case à cocher pour les sous-catégories fonctionne, mais la case à cocher pour la catégorie ne fonctionne pas. Ceux. lorsque vous cliquez sur une catégorie, les sous-catégories s'ouvrent/se ferment. Mais si vous cliquez sur la case à cocher dans la catégorie, rien ne se passe. Regarde.

entrez la description de l'image ici

Comment puis-je résoudre le problème afin que la case à cocher de la catégorie réagisse aux clics.

 class _FilterDialogUserState extends State<FilterDialogUser> {
Map<String, List<String>?> filters = {};
@override
void initState() {
super.initState();
filters = widget.initialState;
}
void _handleCheckFilter(bool checked, String key, String value) {
final currentFilters = filters[key]?? [];
if (checked) {
currentFilters.add(value);
} else {
currentFilters.remove(value);
}
setState(() {
filters[key] = currentFilters;
});
}
List<String> germany = ['Germany'];
List<String> germanyCar = [
'Audi',
'BMW',
'Volkswagen'
];
@override
Widget build(BuildContext context) {
return SimpleDialog(
title: const Text('Filters',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontFamily: 'SuisseIntl',
)),
contentPadding: const EdgeInsets.all(16),
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ExpansionTile(
tilePadding: EdgeInsets.zero,
childrenPadding: EdgeInsets.symmetric(horizontal: 15),
title: CustomCheckboxTile(
value: filters['germany']?.contains(germany)?? false,
onChange: (check) => _handleCheckFilter(
check, 'germany', germany.toString()),
label: germany
.toString()
.replaceAll("[", '')
.replaceAll("]", ''),
),
initiallyExpanded: () {
for (final f in germanyCar) {
if (filters['germanyCar']?.contains(f)?? false) {
return true;
}
}
return false;
}(),
children: germanyCar.map((e) {
return Row(children: [
CustomCheckboxTile(
value: filters['germanyCar']?.contains(e)?? false,
onChange: (check) =>
_handleCheckFilter(check, 'germanyCar', e),
label: e,
),
]);
}).toList(),
),
const SizedBox(
height: 5,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
widget.onApplyFilters(filters);
},
child: const Text('APPLY',
style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
const SizedBox(
height: 5,
),
ElevatedButton(
onPressed: () async {
setState(() {
filters.clear();
});
widget.onApplyFilters(filters);
},
child: const Text('RESET FILTERS',
style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
])
]);
}
}

custom_checkbox_tile.dart

 class CustomCheckboxTile extends StatelessWidget {
final String label;
final bool value;
final void Function(bool)? onChange;
const CustomCheckboxTile({Key? key,
required this.label, required this.value, this.onChange,}): super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Checkbox(
visualDensity: VisualDensity.compact,
value: value,
onChanged: (_) {
if(onChange!= null) {
onChange!(!value);
}
},
),
Text(label),
],
);
}
}


Solution du problème

Voici un exemple complet. Pour moi, il est plus facile et plus propre de gérer des données volumineuses avec ValueNotifierqu'avec setState. Vous pouvez utiliser ce code ou le personnaliser comme vous le souhaitez. Copiez et collez sur le DartPad pour trouver votre réponse.

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Checkbox Expansion Tile Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Home(title: 'Checkbox Expansion Tile Demo'),
);
}
}
class Home extends StatefulWidget {
final String title;
const Home({Key? key, required this.title}): super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
final controller = CountryController([
Country(
name: 'Germany',
cars: [
Car(name: 'Audi'),
Car(name: 'BMW'),
Car(name: 'Volkswagen'),
],
),
Country(
name: 'Sweden',
cars: [
Car(name: 'Koenigsegg'),
Car(name: 'Polestar'),
Car(name: 'Volvo'),
],
),
]);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ValueListenableBuilder<List<Country>>(
valueListenable: controller,
builder: (context, countries, _) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (Country country in countries)
ExpansionTile(
title: Text(country.name),
leading: Checkbox(
value: country.isChecked,
onChanged: (value) {
//controller.checkCountry(country.name);
controller.checkAllByCountry(country.name, value);
},
),
children: [
for (Car car in country.cars)
CustomCheckboxTile(
title: car.name,
value: car.isChecked,
onChanged: (value) {
controller.checkCar(car.name);
},
),
],
),
TextButton(
onPressed: () {
controller.checkAll(true);
},
child: const Text('CHECK ALL'),
),
TextButton(
onPressed: () {
controller.checkAll(false);
},
child: const Text('RESET ALL'),
),
TextButton(
onPressed: () {
print(countries);
},
child: const Text('PRINT ALL'),
),
],
),
),
),
);
}
}
class CustomCheckboxTile extends ListTile {
CustomCheckboxTile({
Key? key,
required String title,
required bool value,
double leftPadding = 28.0,
ValueChanged<bool?>? onChanged,
}): super(
key: key,
title: Text(title),
leading: Padding(
padding: EdgeInsets.only(left: leftPadding),
child: Checkbox(
value: value,
onChanged: onChanged,
),
),
);
}
class Country {
Country({
required this.name,
this.isChecked = false,
this.cars = const [],
});
final String name;
final List<Car> cars;
bool isChecked;
@override
String toString() =>
'Country(name: $name, isChecked: $isChecked, cars: $cars)';
}
class Car {
Car({required this.name, this.isChecked = false});
final String name;
bool isChecked;
@override
String toString() => 'Car(name: $name, isChecked: $isChecked)';
}
class CountryController extends ValueNotifier<List<Country>> {
CountryController(List<Country>? countries): super(countries?? const []);
void checkCountry(String countryName) {
value = [
for (var country in value)
if (country.name == countryName)
Country(
name: country.name,
isChecked: country.isChecked =!country.isChecked,
cars: country.cars,
)
else
country
];
}
void checkCar(String carName) {
value = [
for (var country in value)
Country(
name: country.name,
isChecked: country.isChecked,
cars: [
for (var car in country.cars)
if (car.name == carName)
Car(
name: car.name,
isChecked: car.isChecked =!car.isChecked,
)
else
car
],
),
];
}
void checkAllByCountry(String countryName, bool? isChecked) {
if (isChecked == null) return;
value = [
for (var country in value)
if (country.name == countryName)
Country(
name: country.name,
isChecked: isChecked,
cars: [
for (var car in country.cars)
Car(
name: car.name,
isChecked: isChecked,
),
],
)
else
country
];
}
void checkAll(bool? isChecked) {
value = [
for (var country in value)
Country(
name: country.name,
isChecked: isChecked?? false,
cars: [
for (var car in country.cars)
Car(
name: car.name,
isChecked: isChecked?? false,
),
],
),
];
}
}

Mise à jour

Ajout de cocher tout et réinitialiser tous les boutons.

Aucun commentaire:

Enregistrer un commentaire

Comment utiliseriez-vous .reduce() sur des arguments au lieu d'un tableau ou d'un objet spécifique&nbsp;?

Je veux définir une fonction.flatten qui aplatit plusieurs éléments en un seul tableau. Je sais que ce qui suit n'est pas possible, mais...