Skip to content

Commit 4b818b5

Browse files
authored
Updated document to clarify Clip Behaviour (#157719)
Revised comments to clarify that clipping of child depends on clipBehavior of the parent widget, typically scrollable widgets that default to Clips.hard. Noted that decoration features such as shadows , which render outside the widget boundary, may lead to undesirable effects. Recommended using Clips.none in scenarios where shadow effects are used to avoid clipping issues. Fixing Issue #156819
1 parent e40306f commit 4b818b5

File tree

3 files changed

+214
-2
lines changed

3 files changed

+214
-2
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// Flutter code example for [DecoratedSliver]
8+
/// with clipping turned off in a parent [CustomScrollView].
9+
10+
void main() => runApp(const DecoratedSliverClipExampleApp());
11+
12+
class DecoratedSliverClipExampleApp extends StatelessWidget {
13+
const DecoratedSliverClipExampleApp({super.key});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
return MaterialApp(
18+
title: 'DecoratedSliver Clip Example',
19+
theme: ThemeData(
20+
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
21+
useMaterial3: true,
22+
),
23+
home: const DecoratedSliverClipExample(),
24+
);
25+
}
26+
}
27+
28+
class DecoratedSliverClipExample extends StatefulWidget {
29+
const DecoratedSliverClipExample({super.key});
30+
31+
@override
32+
State<DecoratedSliverClipExample> createState() => _DecoratedSliverClipExampleState();
33+
}
34+
35+
class _DecoratedSliverClipExampleState extends State<DecoratedSliverClipExample> {
36+
double _height = 225.0;
37+
bool _isClipped = false;
38+
39+
@override
40+
Widget build(BuildContext context) {
41+
return Scaffold(
42+
backgroundColor: const Color(0xFF1C1C1C),
43+
body: Column(
44+
children: <Widget>[
45+
Row(
46+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
47+
children: <Widget>[
48+
Switch(
49+
inactiveTrackColor: Colors.cyan,
50+
activeColor: Colors.pink,
51+
onChanged: (bool value) {
52+
setState(() {
53+
_isClipped = value;
54+
});
55+
},
56+
value: _isClipped,
57+
),
58+
Slider(
59+
activeColor: Colors.pink,
60+
inactiveColor: Colors.cyan,
61+
onChanged: (double value) {
62+
setState(() {
63+
_height = value;
64+
});
65+
},
66+
value: _height,
67+
min: 150,
68+
max: 225,
69+
),
70+
],
71+
),
72+
const SizedBox(
73+
height: 20.0,
74+
),
75+
Stack(
76+
children: <Widget>[
77+
Padding(
78+
padding: const EdgeInsets.all(24.0),
79+
child: SizedBox(
80+
width: 400,
81+
height: _height,
82+
child: ResizableCustomScrollView(isClipped: _isClipped),
83+
),
84+
),
85+
Positioned(
86+
top: _height,
87+
left: 0,
88+
right: 0,
89+
child: SizedBox(
90+
height: MediaQuery.of(context).size.height - _height,
91+
width: double.infinity,
92+
),
93+
),
94+
],
95+
),
96+
],
97+
),
98+
);
99+
}
100+
}
101+
102+
class ResizableCustomScrollView extends StatelessWidget {
103+
const ResizableCustomScrollView({
104+
super.key,
105+
required this.isClipped,
106+
});
107+
108+
final bool isClipped;
109+
110+
@override
111+
Widget build(BuildContext context) {
112+
return CustomScrollView(
113+
// The clip behavior defaults to Clip.hardEdge if no argument is provided.
114+
clipBehavior: isClipped ? Clip.hardEdge : Clip.none,
115+
slivers: <Widget>[
116+
DecoratedSliver(
117+
decoration: const ShapeDecoration(
118+
color: Color(0xFF2C2C2C),
119+
shape: RoundedRectangleBorder(
120+
borderRadius: BorderRadius.all(
121+
Radius.circular(6),
122+
),
123+
),
124+
shadows: <BoxShadow>[
125+
BoxShadow(
126+
color: Colors.cyan,
127+
offset: Offset(3, 3),
128+
blurRadius: 24,
129+
),
130+
],
131+
),
132+
sliver: SliverList.builder(
133+
itemCount: 5,
134+
itemBuilder: (_, int index) => Padding(
135+
padding: const EdgeInsets.all(8.0),
136+
child: Row(
137+
children: <Widget>[
138+
const Icon(
139+
Icons.add_box,
140+
color: Color(0xFFA8A8A8),
141+
),
142+
Flexible(
143+
child: Text(
144+
'Item $index',
145+
style: const TextStyle(
146+
color: Color(0xFFA8A8A8),
147+
),
148+
),
149+
),
150+
],
151+
),
152+
),
153+
),
154+
),
155+
],
156+
);
157+
}
158+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/widgets/sliver/decorated_sliver.1.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('CustomScrollView clipBehavior is Clip.none when is Clipped is false', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
const MaterialApp(
13+
home: example.DecoratedSliverClipExample(),
14+
),
15+
);
16+
17+
final CustomScrollView customScrollView = tester.widget(find.byType(CustomScrollView));
18+
19+
expect(customScrollView.clipBehavior, equals(Clip.none));
20+
});
21+
22+
testWidgets('Verify the DecoratedSliver has shadow property in decoration', (WidgetTester tester) async {
23+
await tester.pumpWidget(
24+
const MaterialApp(
25+
home: example.ResizableCustomScrollView(isClipped: false),
26+
),
27+
);
28+
29+
final DecoratedSliver decoratedSliver = tester.widget(find.byType(DecoratedSliver));
30+
final ShapeDecoration shapeDecoration = decoratedSliver.decoration as ShapeDecoration;
31+
32+
expect(shapeDecoration.shadows, isNotEmpty);
33+
});
34+
35+
testWidgets('Verify Slider and Switch widgets', (WidgetTester tester) async {
36+
await tester.pumpWidget(const example.DecoratedSliverClipExampleApp());
37+
38+
expect(find.byType(Slider), findsOneWidget);
39+
40+
expect(find.byType(Switch), findsOneWidget);
41+
});
42+
}

packages/flutter/lib/src/widgets/decorated_sliver.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,27 @@ import 'image.dart';
2525
///
2626
/// Commonly used with [BoxDecoration].
2727
///
28-
/// The [child] is not clipped. To clip a child to the shape of a particular
29-
/// [ShapeDecoration], consider using a [ClipPath] widget.
3028
///
3129
/// {@tool dartpad}
3230
/// This sample shows a radial gradient that draws a moon on a night sky:
3331
///
3432
/// ** See code in examples/api/lib/widgets/sliver/decorated_sliver.0.dart **
3533
/// {@end-tool}
3634
///
35+
/// {@tool dartpad}
36+
/// This example demonstrates how the [CustomScrollView.clipBehavior]
37+
/// impacts a decorated sliver's appearance.
38+
///
39+
/// The [Switch] determines whether clipping is enabled, and
40+
/// the [Slider] adjusts the height of window.
41+
///
42+
/// ** See code in examples/api/lib/widgets/sliver/decorated_sliver.1.dart **
43+
/// {@end-tool}
44+
///
45+
/// This widget does not apply any additional clipping to its [child].
46+
/// To clip a child based on the [Decoration]'s shape, consider using
47+
/// a [ClipPath] widget.
48+
///
3749
/// See also:
3850
///
3951
/// * [DecoratedBox], the version of this class that works with RenderBox widgets.

0 commit comments

Comments
 (0)