Skip to content

Commit 541fdd6

Browse files
authored
DecoratedSliver (#127823)
This is a second attempt to merge #107269. Currently I've fixed two of the issues: 1. Fixed horizontal scrollview by using a switch statement to consider vertical/horizontal case. 2. Fixed issue of `paintExtent` not being the right extent for painting. Rather using a `scrollExtent` for the main axis length of the decoration box and painting it offsetted by the `scrollOffset`. 3. If the sliver child has inifinite scrollExtent, then we only draw the decoration down to the bottom of the `cacheExtent`. The developer is expected to ensure that the border does not creep up above the cache area. This PR includes a test that checks that the correct rectangle is drawn at a certain scrollOffset for both the horizontal and vertical case which should be sufficient for checking that `SliverDecoration` works properly now. Fixes flutter/flutter#107498.
1 parent 0856195 commit 541fdd6

File tree

8 files changed

+781
-0
lines changed

8 files changed

+781
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
void main() => runApp(const SliverDecorationExampleApp());
8+
9+
class SliverDecorationExampleApp extends StatelessWidget {
10+
const SliverDecorationExampleApp({super.key});
11+
12+
@override
13+
Widget build(BuildContext context) {
14+
return MaterialApp(
15+
home: Scaffold(
16+
appBar: AppBar(title: const Text('SliverDecoration Sample')),
17+
body: const SliverDecorationExample(),
18+
),
19+
);
20+
}
21+
}
22+
23+
class SliverDecorationExample extends StatelessWidget {
24+
const SliverDecorationExample({super.key});
25+
26+
@override
27+
Widget build(BuildContext context) {
28+
return CustomScrollView(
29+
slivers: <Widget>[
30+
DecoratedSliver(
31+
decoration: const BoxDecoration(
32+
gradient: RadialGradient(
33+
center: Alignment(-0.5, -0.6),
34+
radius: 0.15,
35+
colors: <Color>[
36+
Color(0xFFEEEEEE),
37+
Color(0xFF111133),
38+
],
39+
stops: <double>[0.9, 1.0],
40+
),
41+
),
42+
sliver: SliverList(
43+
delegate: SliverChildListDelegate(<Widget>[
44+
const Text('Goodnight Moon'),
45+
]),
46+
),
47+
),
48+
const DecoratedSliver(
49+
decoration: BoxDecoration(
50+
color: Colors.amber,
51+
borderRadius: BorderRadius.all(Radius.circular(50))
52+
),
53+
sliver: SliverToBoxAdapter(child: SizedBox(height: 300)),
54+
),
55+
],
56+
);
57+
}
58+
}

packages/flutter/lib/rendering.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export 'src/rendering/custom_layout.dart';
3737
export 'src/rendering/custom_paint.dart';
3838
export 'src/rendering/debug.dart';
3939
export 'src/rendering/debug_overflow_indicator.dart';
40+
export 'src/rendering/decorated_sliver.dart';
4041
export 'src/rendering/editable.dart';
4142
export 'src/rendering/error.dart';
4243
export 'src/rendering/flex.dart';

packages/flutter/lib/src/painting/box_decoration.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ import 'image_provider.dart';
6868
///
6969
/// * [DecoratedBox] and [Container], widgets that can be configured with
7070
/// [BoxDecoration] objects.
71+
/// * [DecoratedSliver], a widget that can be configured with a [BoxDecoration]
72+
/// that is converted to render with slivers.
7173
/// * [CustomPaint], a widget that lets you draw arbitrary graphics.
7274
/// * [Decoration], the base class which lets you define other decorations.
7375
class BoxDecoration extends Decoration {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 'object.dart';
6+
import 'proxy_box.dart';
7+
import 'proxy_sliver.dart';
8+
import 'sliver.dart';
9+
10+
/// Paints a [Decoration] either before or after its child paints.
11+
///
12+
/// If the child has infinite scroll extent, then the [Decoration] paints itself up to the
13+
/// bottom cache extent.
14+
class RenderDecoratedSliver extends RenderProxySliver {
15+
/// Creates a decorated sliver.
16+
///
17+
/// The [decoration], [position], and [configuration] arguments must not be
18+
/// null. By default the decoration paints behind the child.
19+
///
20+
/// The [ImageConfiguration] will be passed to the decoration (with the size
21+
/// filled in) to let it resolve images.
22+
RenderDecoratedSliver({
23+
required Decoration decoration,
24+
DecorationPosition position = DecorationPosition.background,
25+
ImageConfiguration configuration = ImageConfiguration.empty,
26+
}) : _decoration = decoration,
27+
_position = position,
28+
_configuration = configuration;
29+
30+
/// What decoration to paint.
31+
///
32+
/// Commonly a [BoxDecoration].
33+
Decoration get decoration => _decoration;
34+
Decoration _decoration;
35+
set decoration(Decoration value) {
36+
if (value == decoration) {
37+
return;
38+
}
39+
_decoration = value;
40+
_painter?.dispose();
41+
_painter = decoration.createBoxPainter(markNeedsPaint);
42+
markNeedsPaint();
43+
}
44+
45+
/// Whether to paint the box decoration behind or in front of the child.
46+
DecorationPosition get position => _position;
47+
DecorationPosition _position;
48+
set position(DecorationPosition value) {
49+
if (value == position) {
50+
return;
51+
}
52+
_position = value;
53+
markNeedsPaint();
54+
}
55+
56+
/// The settings to pass to the decoration when painting, so that it can
57+
/// resolve images appropriately. See [ImageProvider.resolve] and
58+
/// [BoxPainter.paint].
59+
///
60+
/// The [ImageConfiguration.textDirection] field is also used by
61+
/// direction-sensitive [Decoration]s for painting and hit-testing.
62+
ImageConfiguration get configuration => _configuration;
63+
ImageConfiguration _configuration;
64+
set configuration(ImageConfiguration value) {
65+
if (value == configuration) {
66+
return;
67+
}
68+
_configuration = value;
69+
markNeedsPaint();
70+
}
71+
72+
BoxPainter? _painter;
73+
74+
@override
75+
void attach(covariant PipelineOwner owner) {
76+
_painter = decoration.createBoxPainter(markNeedsPaint);
77+
super.attach(owner);
78+
}
79+
80+
@override
81+
void detach() {
82+
_painter?.dispose();
83+
_painter = null;
84+
super.detach();
85+
}
86+
87+
@override
88+
void dispose() {
89+
_painter?.dispose();
90+
_painter = null;
91+
super.dispose();
92+
}
93+
94+
@override
95+
void paint(PaintingContext context, Offset offset) {
96+
if (child != null && child!.geometry!.visible) {
97+
final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
98+
final Size childSize;
99+
final Offset scrollOffset;
100+
101+
// In the case where the child sliver has infinite scroll extent, the decoration
102+
// should only extend down to the bottom cache extent.
103+
final double cappedMainAxisExtent = child!.geometry!.scrollExtent.isInfinite
104+
? constraints.scrollOffset + child!.geometry!.cacheExtent + constraints.cacheOrigin
105+
: child!.geometry!.scrollExtent;
106+
switch (constraints.axis) {
107+
case Axis.vertical:
108+
childSize = Size(constraints.crossAxisExtent, cappedMainAxisExtent);
109+
scrollOffset = Offset(0.0, -constraints.scrollOffset);
110+
case Axis.horizontal:
111+
childSize = Size(cappedMainAxisExtent, constraints.crossAxisExtent);
112+
scrollOffset = Offset(-constraints.scrollOffset, 0.0);
113+
}
114+
final Offset childOffset = offset + childParentData.paintOffset;
115+
if (position == DecorationPosition.background) {
116+
_painter!.paint(context.canvas, childOffset + scrollOffset, configuration.copyWith(size: childSize));
117+
}
118+
context.paintChild(child!, childOffset);
119+
if (position == DecorationPosition.foreground) {
120+
_painter!.paint(context.canvas, childOffset + scrollOffset, configuration.copyWith(size: childSize));
121+
}
122+
}
123+
}
124+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import 'image.dart';
5252
/// * [Decoration], which you can extend to provide other effects with
5353
/// [DecoratedBox].
5454
/// * [CustomPaint], another way to draw custom effects from the widget layer.
55+
/// * [DecoratedSliver], which applies a [Decoration] to a sliver.
5556
class DecoratedBox extends SingleChildRenderObjectWidget {
5657
/// Creates a widget that paints a [Decoration].
5758
///
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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/rendering.dart';
6+
7+
import 'basic.dart';
8+
import 'framework.dart';
9+
import 'image.dart';
10+
11+
/// A sliver widget that paints a [Decoration] either before or after its child
12+
/// paints.
13+
///
14+
/// Unlike [DecoratedBox], this widget expects its child to be a sliver, and
15+
/// must be placed in a widget that expects a sliver.
16+
///
17+
/// If the child sliver has infinite [SliverGeometry.scrollExtent], then we only
18+
/// draw the decoration down to the bottom [SliverGeometry.cacheExtent], and
19+
/// it is necessary to ensure that the bottom border does not creep
20+
/// above the top of the bottom cache. This can happen if the bottom has a
21+
/// border radius larger than the extent of the cache area.
22+
///
23+
/// Commonly used with [BoxDecoration].
24+
///
25+
/// The [child] is not clipped. To clip a child to the shape of a particular
26+
/// [ShapeDecoration], consider using a [ClipPath] widget.
27+
///
28+
/// {@tool dartpad}
29+
/// This sample shows a radial gradient that draws a moon on a night sky:
30+
///
31+
/// ** See code in examples/api/lib/widgets/sliver/decorated_sliver.0.dart **
32+
/// {@end-tool}
33+
///
34+
/// See also:
35+
///
36+
/// * [DecoratedBox], the version of this class that works with RenderBox widgets.
37+
/// * [Decoration], which you can extend to provide other effects with
38+
/// [DecoratedSliver].
39+
/// * [CustomPaint], another way to draw custom effects from the widget layer.
40+
class DecoratedSliver extends SingleChildRenderObjectWidget {
41+
/// Creates a widget that paints a [Decoration].
42+
///
43+
/// The [decoration] and [position] arguments must not be null. By default the
44+
/// decoration paints behind the child.
45+
const DecoratedSliver({
46+
super.key,
47+
required this.decoration,
48+
this.position = DecorationPosition.background,
49+
Widget? sliver,
50+
}) : super(child: sliver);
51+
52+
/// What decoration to paint.
53+
///
54+
/// Commonly a [BoxDecoration].
55+
final Decoration decoration;
56+
57+
/// Whether to paint the box decoration behind or in front of the child.
58+
final DecorationPosition position;
59+
60+
@override
61+
RenderDecoratedSliver createRenderObject(BuildContext context) {
62+
return RenderDecoratedSliver(
63+
decoration: decoration,
64+
position: position,
65+
configuration: createLocalImageConfiguration(context),
66+
);
67+
}
68+
69+
@override
70+
void updateRenderObject(BuildContext context, RenderDecoratedSliver renderObject) {
71+
renderObject
72+
..decoration = decoration
73+
..position = position
74+
..configuration = createLocalImageConfiguration(context);
75+
}
76+
77+
@override
78+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
79+
super.debugFillProperties(properties);
80+
final String label;
81+
switch (position) {
82+
case DecorationPosition.background:
83+
label = 'bg';
84+
case DecorationPosition.foreground:
85+
label = 'fg';
86+
}
87+
properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden));
88+
properties.add(DiagnosticsProperty<Decoration>(label, decoration));
89+
}
90+
}

packages/flutter/lib/widgets.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export 'src/widgets/container.dart';
3838
export 'src/widgets/context_menu_button_item.dart';
3939
export 'src/widgets/context_menu_controller.dart';
4040
export 'src/widgets/debug.dart';
41+
export 'src/widgets/decorated_sliver.dart';
4142
export 'src/widgets/default_selection_style.dart';
4243
export 'src/widgets/default_text_editing_shortcuts.dart';
4344
export 'src/widgets/desktop_text_selection_toolbar_layout_delegate.dart';

0 commit comments

Comments
 (0)