Add ability to clip Stepper
step content (#152370)
fixes [Dismissible content overlays Stepper interface while dismissing it](https://github.com/flutter/flutter/issues/66007) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { final List<String> items = List<String>.generate(20, (int i) => 'Item ${i + 1}'); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Padding( padding: const EdgeInsets.all(20), child: DecoratedBox( decoration: BoxDecoration( border: Border.all(color: Colors.amber, width: 2), ), child: Padding( padding: const EdgeInsets.all(2.0), child: Column( children: <Widget>[ const SizedBox(height: 8.0), Text( 'Dismissible Widget - Vertical Stepper Widget', style: Theme.of(context).textTheme.titleLarge, ), Expanded( child: Stepper( clipBehavior: Clip.hardEdge, steps: <Step>[ Step( isActive: true, title: const Text('Step 1'), content: ColoredBox( color: Colors.black12, child: ListView.builder( itemCount: items.length, shrinkWrap: true, itemBuilder: (BuildContext context, int index) { final String item = items[index]; return Dismissible( key: Key(item), onDismissed: (DismissDirection direction) { setState(() { items.removeAt(index); }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('$item dismissed'))); }, background: Container(color: Colors.red), child: ListTile(title: Text(item)), ); }, ), ), ), const Step( title: Text('Step 2'), content: Text('content'), ), ], ), ), const Divider(height: 1), const SizedBox(height: 8.0), Text( 'Dismissible Widget - Horizontal Stepper Widget', style: Theme.of(context).textTheme.titleLarge, ), Expanded( child: Stepper( clipBehavior: Clip.hardEdge, type: StepperType.horizontal, elevation: 0.0, steps: <Step>[ Step( isActive: true, title: const Text('Step 1'), content: ColoredBox( color: Colors.black12, child: ListView.builder( itemCount: items.length, shrinkWrap: true, itemBuilder: (BuildContext context, int index) { final String item = items[index]; return Dismissible( key: Key(item), onDismissed: (DismissDirection direction) { setState(() { items.removeAt(index); }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('$item dismissed'))); }, background: Container(color: Colors.red), child: ListTile(title: Text(item)), ); }, ), ), ), const Step( title: Text('Step 2'), content: Text('content'), ), ], ), ), ], ), ), ), ), ), ); } } ``` </details> ### Without `Stepper` step content clipping  ### With `Stepper` step content clipping 
This commit is contained in:
parent
91a3f69f11
commit
0d154e55d1
@ -222,6 +222,7 @@ class Stepper extends StatefulWidget {
|
||||
this.stepIconHeight,
|
||||
this.stepIconWidth,
|
||||
this.stepIconMargin,
|
||||
this.clipBehavior = Clip.none,
|
||||
}) : assert(0 <= currentStep && currentStep < steps.length),
|
||||
assert(stepIconHeight == null || (stepIconHeight >= _kStepSize && stepIconHeight <= _kMaxStepSize),
|
||||
'stepIconHeight must be greater than $_kStepSize and less or equal to $_kMaxStepSize'),
|
||||
@ -366,6 +367,15 @@ class Stepper extends StatefulWidget {
|
||||
/// Overrides the default step icon margin.
|
||||
final EdgeInsets? stepIconMargin;
|
||||
|
||||
/// The [Step.content] will be clipped to this Clip type.
|
||||
///
|
||||
/// Defaults to [Clip.none].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Clip], which explains how to use this property.
|
||||
final Clip clipBehavior;
|
||||
|
||||
@override
|
||||
State<Stepper> createState() => _StepperState();
|
||||
}
|
||||
@ -795,7 +805,10 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
widget.steps[index].content,
|
||||
ClipRect(
|
||||
clipBehavior: widget.clipBehavior,
|
||||
child: widget.steps[index].content,
|
||||
),
|
||||
_buildVerticalControls(index),
|
||||
],
|
||||
),
|
||||
@ -888,7 +901,10 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
||||
Visibility(
|
||||
maintainState: true,
|
||||
visible: i == widget.currentStep,
|
||||
child: widget.steps[i].content,
|
||||
child: ClipRect(
|
||||
clipBehavior: widget.clipBehavior,
|
||||
child: widget.steps[i].content,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1720,6 +1720,91 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async
|
||||
));
|
||||
expect(lastConnector.width, equals(0.0));
|
||||
});
|
||||
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/66007.
|
||||
testWidgets('Default Stepper clipBehavior', (WidgetTester tester) async {
|
||||
Widget buildStepper({ required StepperType type }) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Stepper(
|
||||
type: type,
|
||||
steps: const <Step>[
|
||||
Step(
|
||||
title: Text('step1'),
|
||||
content: Text('step1 content'),
|
||||
),
|
||||
Step(
|
||||
title: Text('step2'),
|
||||
content: Text('step2 content'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ClipRect getContentClipRect() {
|
||||
return tester.widget<ClipRect>(find.ancestor(
|
||||
of: find.text('step1 content'),
|
||||
matching: find.byType(ClipRect),
|
||||
).first);
|
||||
}
|
||||
|
||||
// Test vertical stepper with default clipBehavior.
|
||||
await tester.pumpWidget(buildStepper(type: StepperType.vertical));
|
||||
|
||||
expect(getContentClipRect().clipBehavior, equals(Clip.none));
|
||||
|
||||
// Test horizontal stepper with default clipBehavior.
|
||||
await tester.pumpWidget(buildStepper(type: StepperType.horizontal));
|
||||
|
||||
expect(getContentClipRect().clipBehavior, equals(Clip.none));
|
||||
});
|
||||
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/66007.
|
||||
testWidgets('Stepper steps can be clipped', (WidgetTester tester) async {
|
||||
Widget buildStepper({ required StepperType type, required Clip clipBehavior }) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Stepper(
|
||||
clipBehavior: clipBehavior,
|
||||
type: type,
|
||||
steps: const <Step>[
|
||||
Step(
|
||||
title: Text('step1'),
|
||||
content: Text('step1 content'),
|
||||
),
|
||||
Step(
|
||||
title: Text('step2'),
|
||||
content: Text('step2 content'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ClipRect getContentClipRect() {
|
||||
return tester.widget<ClipRect>(find.ancestor(
|
||||
of: find.text('step1 content'),
|
||||
matching: find.byType(ClipRect),
|
||||
).first);
|
||||
}
|
||||
|
||||
// Test vertical stepper with clipBehavior set to Clip.hardEdge.
|
||||
await tester.pumpWidget(buildStepper(type: StepperType.vertical, clipBehavior: Clip.hardEdge));
|
||||
|
||||
expect(getContentClipRect().clipBehavior, equals(Clip.hardEdge));
|
||||
|
||||
// Test horizontal stepper with clipBehavior set to Clip.hardEdge.
|
||||
await tester.pumpWidget(buildStepper(type: StepperType.horizontal, clipBehavior: Clip.hardEdge));
|
||||
|
||||
expect(getContentClipRect().clipBehavior, equals(Clip.hardEdge));
|
||||
});
|
||||
}
|
||||
|
||||
class _TappableColorWidget extends StatefulWidget {
|
||||
|
Loading…
x
Reference in New Issue
Block a user