[Stepper] adds stepIconBuilder property (#122816)
[Stepper] adds stepIconBuilder property
diff --git a/packages/flutter/lib/src/material/stepper.dart b/packages/flutter/lib/src/material/stepper.dart
index 4348a18..8978fe6 100644
--- a/packages/flutter/lib/src/material/stepper.dart
+++ b/packages/flutter/lib/src/material/stepper.dart
@@ -106,6 +106,10 @@
/// * [WidgetBuilder], which is similar but only takes a [BuildContext].
typedef ControlsWidgetBuilder = Widget Function(BuildContext context, ControlsDetails details);
+/// A builder that creates the icon widget for the [Step] at [stepIndex], given
+/// [stepState].
+typedef StepIconBuilder = Widget? Function(int stepIndex, StepState stepState);
+
const TextStyle _kStepStyle = TextStyle(
fontSize: 12.0,
color: Colors.white,
@@ -207,6 +211,7 @@
this.controlsBuilder,
this.elevation,
this.margin,
+ this.stepIconBuilder,
}) : assert(0 <= currentStep && currentStep < steps.length);
/// The steps of the stepper whose titles, subtitles, icons always get shown.
@@ -303,9 +308,17 @@
/// The elevation of this stepper's [Material] when [type] is [StepperType.horizontal].
final double? elevation;
- /// custom margin on vertical stepper.
+ /// Custom margin on vertical stepper.
final EdgeInsetsGeometry? margin;
+ /// Callback for creating custom icons for the [steps].
+ ///
+ /// When overriding icon for [StepState.error], please return
+ /// a widget whose width and height are 14 pixels or less to avoid overflow.
+ ///
+ /// If null, the default icons will be used for respective [StepState].
+ final StepIconBuilder? stepIconBuilder;
+
@override
State<Stepper> createState() => _StepperState();
}
@@ -373,6 +386,10 @@
Widget _buildCircleChild(int index, bool oldState) {
final StepState state = oldState ? _oldStates[index]! : widget.steps[index].state;
final bool isDarkActive = _isDark() && widget.steps[index].isActive;
+ final Widget? icon = widget.stepIconBuilder?.call(index, state);
+ if (icon != null) {
+ return icon;
+ }
switch (state) {
case StepState.indexed:
case StepState.disabled:
diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart
index e1472be..03f2c5c 100644
--- a/packages/flutter/test/material/stepper_test.dart
+++ b/packages/flutter/test/material/stepper_test.dart
@@ -1259,6 +1259,48 @@
tester.widget<Text>(find.text('Label ${index + 2}'));
expect(bodyMediumStyle, nextLabelTextWidget.style);
});
+
+ testWidgets('Stepper stepIconBuilder test', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Material(
+ child: Stepper(
+ stepIconBuilder: (int index, StepState state) {
+ if (state == StepState.complete) {
+ return const FlutterLogo(size: 18);
+ }
+ return null;
+ },
+ steps: const <Step>[
+ Step(
+ title: Text('A'),
+ state: StepState.complete,
+ content: SizedBox(width: 100.0, height: 100.0),
+ ),
+ Step(
+ title: Text('B'),
+ state: StepState.editing,
+ content: SizedBox(width: 100.0, height: 100.0),
+ ),
+ Step(
+ title: Text('C'),
+ state: StepState.error,
+ content: SizedBox(width: 100.0, height: 100.0),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ /// Finds the overridden widget for StepState.complete
+ expect(find.byType(FlutterLogo), findsOneWidget);
+
+ /// StepState.editing and StepState.error should have a default icon
+ expect(find.byIcon(Icons.edit), findsOneWidget);
+ expect(find.text('!'), findsOneWidget);
+ });
+
}
class _TappableColorWidget extends StatefulWidget {