Quiero escribir filtros con categorías y subcategorías que pueden tener la forma de una lista desplegable. Escribí esto con un ExpansionTile y puse una casilla de verificación en el título del ExpansionTile. Y me encontré con el problema de que la casilla de verificación para subcategorías funciona, pero la casilla de verificación para la categoría no funciona. Aquellas. cuando hace clic en una categoría, las subcategorías se abren / cierran. Pero si hace clic en la casilla de verificación en la categoría, no sucede nada. Echar un vistazo.
¿Cómo puedo solucionar el problema para que la casilla de verificación en la categoría reaccione a los 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),
],
);
}
}
Solución del problema
Aquí hay un ejemplo completo. Para mí, es más fácil y más limpio manejar datos de gran tamaño ValueNotifier
que con setState
. Puedes usar este código o personalizarlo como quieras. Copie y pegue en el DartPad para encontrar su respuesta.
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,
),
],
),
];
}
}
Actualizar
Se agregó verificar todo y restablecer todos los botones.
No hay comentarios:
Publicar un comentario