diff --git a/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart b/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart new file mode 100644 index 00000000000..1b3e86859d1 --- /dev/null +++ b/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart @@ -0,0 +1,158 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flutter code sample for ToggleButtons + +import 'package:flutter/material.dart'; + +const List fruits = [ + Text('Apple'), + Text('Banana'), + Text('Orange') +]; + +const List vegetables = [ + Text('Tomatoes'), + Text('Potatoes'), + Text('Carrots') +]; + +const List icons = [ + Icon(Icons.sunny), + Icon(Icons.cloud), + Icon(Icons.ac_unit), +]; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + static const String _title = 'ToggleButtons Sample'; + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: _title, + home: ToggleButtonsSample(title: _title), + ); + } +} + +class ToggleButtonsSample extends StatefulWidget { + const ToggleButtonsSample({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + State createState() => _ToggleButtonsSampleState(); +} + +class _ToggleButtonsSampleState extends State { + final List _selectedFruits = [true, false, false]; + final List _selectedVegetables = [false, true, false]; + final List _selectedWeather = [false, false, true]; + bool vertical = false; + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: Center( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // ToggleButtons with a single selection. + Text('Single-select', style: theme.textTheme.subtitle2), + const SizedBox(height: 5), + ToggleButtons( + direction: vertical ? Axis.vertical : Axis.horizontal, + onPressed: (int index) { + setState(() { + // The button that is tapped is set to true, and the others to false. + for (int i = 0; i < _selectedFruits.length; i++) { + _selectedFruits[i] = i == index; + } + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: Colors.red[700], + selectedColor: Colors.white, + fillColor: Colors.red[200], + color: Colors.red[400], + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 80.0, + ), + isSelected: _selectedFruits, + children: fruits, + ), + const SizedBox(height: 20), + // ToggleButtons with a multiple selection. + Text('Multi-select', style: theme.textTheme.subtitle2), + const SizedBox(height: 5), + ToggleButtons( + direction: vertical ? Axis.vertical : Axis.horizontal, + onPressed: (int index) { + // All buttons are selectable. + setState(() { + _selectedVegetables[index] = + !_selectedVegetables[index]; + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: Colors.green[700], + selectedColor: Colors.white, + fillColor: Colors.green[200], + color: Colors.green[400], + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 80.0, + ), + isSelected: _selectedVegetables, + children: vegetables, + ), + const SizedBox(height: 20), + // ToggleButtons with icons only. + Text('Icon-only', style: theme.textTheme.subtitle2), + const SizedBox(height: 5), + ToggleButtons( + direction: vertical ? Axis.vertical : Axis.horizontal, + onPressed: (int index) { + setState(() { + // The button that is tapped is set to true, and the others to false. + for (int i = 0; i < _selectedWeather.length; i++) { + _selectedWeather[i] = i == index; + } + }); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: Colors.blue[700], + selectedColor: Colors.white, + fillColor: Colors.blue[200], + color: Colors.blue[400], + isSelected: _selectedWeather, + children: icons, + ), + ], + ), + ), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + setState(() { + // When the button is pressed, ToggleButtons direction is changed. + vertical = !vertical; + }); + }, + icon: const Icon(Icons.screen_rotation_outlined), + label: Text(vertical ? 'Horizontal' : 'Vertical'), + ), + ); + } +} diff --git a/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart b/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart new file mode 100644 index 00000000000..d1d15e653a3 --- /dev/null +++ b/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart @@ -0,0 +1,118 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/toggle_buttons/toggle_buttons.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + + testWidgets('Single-select ToggleButtons', (WidgetTester tester) async { + TextButton findButton(String text) { + return tester.widget(find.widgetWithText(TextButton, text)); + } + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: example.MyApp(), + ), + ), + ); + + TextButton firstButton = findButton('Apple'); + TextButton secondButton = findButton('Banana'); + TextButton thirdButton = findButton('Orange'); + + /// First button is selected. + expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0xffef9a9a)); + expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + + /// Tap on second button. + await tester.tap(find.widgetWithText(TextButton, 'Banana')); + await tester.pumpAndSettle(); + + firstButton = findButton('Apple'); + secondButton = findButton('Banana'); + thirdButton = findButton('Orange'); + + /// Only second button is selected. + expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0xffef9a9a)); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + }); + + testWidgets('Multi-select ToggleButtons', (WidgetTester tester) async { + TextButton findButton(String text) { + return tester.widget(find.widgetWithText(TextButton, text)); + } + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: example.MyApp(), + ), + ), + ); + + TextButton firstButton = findButton('Tomatoes'); + TextButton secondButton = findButton('Potatoes'); + TextButton thirdButton = findButton('Carrots'); + + /// Second button is selected. + expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + + /// Tap on other two buttons. + await tester.tap(find.widgetWithText(TextButton, 'Tomatoes')); + await tester.tap(find.widgetWithText(TextButton, 'Carrots')); + await tester.pumpAndSettle(); + + firstButton = findButton('Tomatoes'); + secondButton = findButton('Potatoes'); + thirdButton = findButton('Carrots'); + + /// All buttons are selected. + expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); + expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); + }); + + testWidgets('Icon-only ToggleButtons', (WidgetTester tester) async { + TextButton findButton(IconData iconData) { + return tester.widget(find.widgetWithIcon(TextButton, iconData)); + } + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: example.MyApp(), + ), + ), + ); + + TextButton firstButton = findButton(Icons.sunny); + TextButton secondButton = findButton(Icons.cloud); + TextButton thirdButton = findButton(Icons.ac_unit); + + /// Third button is selected. + expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0xff90caf9)); + + /// Tap on the first button. + await tester.tap(find.widgetWithIcon(TextButton, Icons.sunny)); + await tester.pumpAndSettle(); + + firstButton = findButton(Icons.sunny); + secondButton = findButton(Icons.cloud); + thirdButton = findButton(Icons.ac_unit); + + /// First button os selected. + expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0xff90caf9)); + expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + }); +} + +Set enabled = { }; diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart index 79e0406bcc4..be0f8cdd80d 100644 --- a/packages/flutter/lib/src/material/toggle_buttons.dart +++ b/packages/flutter/lib/src/material/toggle_buttons.dart @@ -28,6 +28,12 @@ import 'toggle_buttons_theme.dart'; /// /// {@youtube 560 315 https://www.youtube.com/watch?v=kVEguaQWGAY} /// +/// {@tool dartpad} +/// This example showcase [ToggleButtons] in various configurations. +/// +/// ** See code in examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart ** +/// {@end-tool} +/// /// ## Customizing toggle buttons /// Each toggle's behavior can be configured by the [onPressed] callback, which /// can update the [isSelected] list however it wants to.