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 | return; |
98 | } |
99 | // In the case where the child sliver has infinite scroll extent, the decoration |
100 | // should only extend down to the bottom cache extent. |
101 | final double cappedMainAxisExtent = child!.geometry!.scrollExtent.isInfinite |
102 | ? constraints.scrollOffset + child!.geometry!.cacheExtent + constraints.cacheOrigin |
103 | : child!.geometry!.scrollExtent; |
104 | final (Size childSize, Offset scrollOffset) = switch (constraints.axis) { |
105 | Axis.horizontal => ( |
106 | Size(cappedMainAxisExtent, constraints.crossAxisExtent), |
107 | Offset(-constraints.scrollOffset, 0.0), |
108 | ), |
109 | Axis.vertical => ( |
110 | Size(constraints.crossAxisExtent, cappedMainAxisExtent), |
111 | Offset(0.0, -constraints.scrollOffset), |
112 | ), |
113 | }; |
114 | offset += (child!.parentData! as SliverPhysicalParentData).paintOffset; |
115 | void paintDecoration() => _painter!.paint( |
116 | context.canvas, |
117 | offset + scrollOffset, |
118 | configuration.copyWith(size: childSize), |
119 | ); |
120 | switch (position) { |
121 | case DecorationPosition.background: |
122 | paintDecoration(); |
123 | context.paintChild(child!, offset); |
124 | case DecorationPosition.foreground: |
125 | context.paintChild(child!, offset); |
126 | paintDecoration(); |
127 | } |
128 | } |
129 | } |
130 | |