Customize color and thickness of connected lines in Stepper.dart (#122485)
This commit is contained in:
@@ -211,6 +211,8 @@ class Stepper extends StatefulWidget {
|
||||
this.controlsBuilder,
|
||||
this.elevation,
|
||||
this.margin,
|
||||
this.connectorColor,
|
||||
this.connectorThickness,
|
||||
this.stepIconBuilder,
|
||||
}) : assert(0 <= currentStep && currentStep < steps.length);
|
||||
|
||||
@@ -311,6 +313,19 @@ class Stepper extends StatefulWidget {
|
||||
/// Custom margin on vertical stepper.
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
/// Customize connected lines colors.
|
||||
///
|
||||
/// Resolves in the following states:
|
||||
/// * [MaterialState.selected].
|
||||
/// * [MaterialState.disabled].
|
||||
///
|
||||
/// If not set then the widget will use default colors, primary for selected state
|
||||
/// and grey.shade400 for disabled state.
|
||||
final MaterialStateProperty<Color>? connectorColor;
|
||||
|
||||
/// The thickness of the connecting lines.
|
||||
final double? connectorThickness;
|
||||
|
||||
/// Callback for creating custom icons for the [steps].
|
||||
///
|
||||
/// When overriding icon for [StepState.error], please return
|
||||
@@ -375,11 +390,23 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget _buildLine(bool visible) {
|
||||
Color _connectorColor(bool isActive) {
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
final Set<MaterialState> states = <MaterialState>{
|
||||
if (isActive) MaterialState.selected else MaterialState.disabled,
|
||||
};
|
||||
final Color? resolvedConnectorColor = widget.connectorColor?.resolve(states);
|
||||
if (resolvedConnectorColor != null) {
|
||||
return resolvedConnectorColor;
|
||||
}
|
||||
return isActive ? colorScheme.primary : Colors.grey.shade400;
|
||||
}
|
||||
|
||||
Widget _buildLine(bool visible, bool isActive) {
|
||||
return Container(
|
||||
width: visible ? 1.0 : 0.0,
|
||||
width: visible ? widget.connectorThickness ?? 1.0 : 0.0,
|
||||
height: 16.0,
|
||||
color: Colors.grey.shade400,
|
||||
color: _connectorColor(isActive),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -415,11 +442,19 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Color _circleColor(int index) {
|
||||
final bool isActive = widget.steps[index].isActive;
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
final Set<MaterialState> states = <MaterialState>{
|
||||
if (isActive) MaterialState.selected else MaterialState.disabled,
|
||||
};
|
||||
final Color? resolvedConnectorColor = widget.connectorColor?.resolve(states);
|
||||
if (resolvedConnectorColor != null) {
|
||||
return resolvedConnectorColor;
|
||||
}
|
||||
if (!_isDark()) {
|
||||
return widget.steps[index].isActive ? colorScheme.primary : colorScheme.onSurface.withOpacity(0.38);
|
||||
return isActive ? colorScheme.primary : colorScheme.onSurface.withOpacity(0.38);
|
||||
} else {
|
||||
return widget.steps[index].isActive ? colorScheme.secondary : colorScheme.background;
|
||||
return isActive ? colorScheme.secondary : colorScheme.background;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,6 +694,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget _buildVerticalHeader(int index) {
|
||||
final bool isActive = widget.steps[index].isActive;
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Row(
|
||||
@@ -667,9 +703,9 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
children: <Widget>[
|
||||
// Line parts are always added in order for the ink splash to
|
||||
// flood the tips of the connector lines.
|
||||
_buildLine(!_isFirst(index)),
|
||||
_buildLine(!_isFirst(index), isActive),
|
||||
_buildIcon(index),
|
||||
_buildLine(!_isLast(index)),
|
||||
_buildLine(!_isLast(index), isActive),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
@@ -694,9 +730,9 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
width: 24.0,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: _isLast(index) ? 0.0 : 1.0,
|
||||
width: widget.connectorThickness ?? 1.0,
|
||||
child: Container(
|
||||
color: Colors.grey.shade400,
|
||||
color: _connectorColor(widget.steps[index].isActive),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -789,9 +825,10 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
if (!_isLast(i))
|
||||
Expanded(
|
||||
child: Container(
|
||||
key: Key('line$i'),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
height: 1.0,
|
||||
color: Colors.grey.shade400,
|
||||
height: widget.connectorThickness ?? 1.0,
|
||||
color: _connectorColor(widget.steps[i+1].isActive),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1260,6 +1260,75 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async
|
||||
expect(bodyMediumStyle, nextLabelTextWidget.style);
|
||||
});
|
||||
|
||||
testWidgets('Stepper Connector Style', (WidgetTester tester) async {
|
||||
const Color selectedColor = Colors.black;
|
||||
const Color disabledColor = Colors.white;
|
||||
int index = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Stepper(
|
||||
type: StepperType.horizontal,
|
||||
connectorColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) =>
|
||||
states.contains(MaterialState.selected)
|
||||
? selectedColor
|
||||
: disabledColor),
|
||||
onStepTapped: (int i) => setState(() => index = i),
|
||||
currentStep: index,
|
||||
steps: <Step>[
|
||||
Step(
|
||||
isActive: index >= 0,
|
||||
title: const Text('step1'),
|
||||
content: const Text('step1 content'),
|
||||
),
|
||||
Step(
|
||||
isActive: index >= 1,
|
||||
title: const Text('step2'),
|
||||
content: const Text('step2 content'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
Color? circleColor(String circleText) => (tester.widget<AnimatedContainer>(
|
||||
find.widgetWithText(AnimatedContainer, circleText),
|
||||
).decoration as BoxDecoration?)?.color;
|
||||
|
||||
Color? lineColor(String keyStep) => tester.widget<Container>(find.byKey(Key(keyStep))).color;
|
||||
|
||||
// Step 1
|
||||
// check if I'm in step 1
|
||||
expect(find.text('step1 content'), findsOneWidget);
|
||||
expect(find.text('step2 content'), findsNothing);
|
||||
|
||||
expect(circleColor('1'), selectedColor);
|
||||
expect(circleColor('2'), disabledColor);
|
||||
// in two steps case there will be single line
|
||||
expect(lineColor('line0'), disabledColor);
|
||||
|
||||
// now hitting step two
|
||||
await tester.tap(find.text('step2'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// check if I'm in step 1
|
||||
expect(find.text('step1 content'), findsNothing);
|
||||
expect(find.text('step2 content'), findsOneWidget);
|
||||
|
||||
expect(circleColor('1'), selectedColor);
|
||||
expect(circleColor('2'), selectedColor);
|
||||
|
||||
expect(lineColor('line0'), selectedColor);
|
||||
});
|
||||
|
||||
testWidgets('Stepper stepIconBuilder test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
||||
Reference in New Issue
Block a user