-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathink_decoration.dart
375 lines (351 loc) · 12.6 KB
/
ink_decoration.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'material.dart';
/// A convenience widget for drawing images and other decorations on [Material]
/// widgets, so that [InkWell] and [InkResponse] splashes will render over them.
///
/// Ink splashes and highlights, as rendered by [InkWell] and [InkResponse],
/// draw on the actual underlying [Material], under whatever widgets are drawn
/// over the material (such as [Text] and [Icon]s). If an opaque image is drawn
/// over the [Material] (maybe using a [Container] or [DecoratedBox]), these ink
/// effects will not be visible, as they will be entirely obscured by the opaque
/// graphics drawn above the [Material].
///
/// This widget draws the given [Decoration] directly on the [Material], in the
/// same way that [InkWell] and [InkResponse] draw there. This allows the
/// splashes to be drawn above the otherwise opaque graphics.
///
/// An alternative solution is to use a [MaterialType.transparency] material
/// above the opaque graphics, so that the ink responses from [InkWell]s and
/// [InkResponse]s will be drawn on the transparent material on top of the
/// opaque graphics, rather than under the opaque graphics on the underlying
/// [Material].
///
/// ## Limitations
///
/// This widget is subject to the same limitations as other ink effects, as
/// described in the documentation for [Material]. Most notably, the position of
/// an [Ink] widget must not change during the lifetime of the [Material] object
/// unless a [LayoutChangedNotification] is dispatched each frame that the
/// position changes. This is done automatically for [ListView] and other
/// scrolling widgets, but is not done for animated transitions such as
/// [SlideTransition].
///
/// Additionally, if multiple [Ink] widgets paint on the same [Material] in the
/// same location, their relative order is not guaranteed. The decorations will
/// be painted in the order that they were added to the material, which
/// generally speaking will match the order they are given in the widget tree,
/// but this order may appear to be somewhat random in more dynamic situations.
///
/// {@tool snippet}
///
/// This example shows how a [Material] widget can have a yellow rectangle drawn
/// on it using [Ink], while still having ink effects over the yellow rectangle:
///
/// ```dart
/// Material(
/// color: Colors.teal[900],
/// child: Center(
/// child: Ink(
/// color: Colors.yellow,
/// width: 200.0,
/// height: 100.0,
/// child: InkWell(
/// onTap: () { /* ... */ },
/// child: Center(
/// child: Text('YELLOW'),
/// )
/// ),
/// ),
/// ),
/// )
/// ```
/// {@end-tool}
/// {@tool snippet}
///
/// The following example shows how an image can be printed on a [Material]
/// widget with an [InkWell] above it:
///
/// ```dart
/// Material(
/// color: Colors.grey[800],
/// child: Center(
/// child: Ink.image(
/// image: AssetImage('cat.jpeg'),
/// fit: BoxFit.cover,
/// width: 300.0,
/// height: 200.0,
/// child: InkWell(
/// onTap: () { /* ... */ },
/// child: Align(
/// alignment: Alignment.topLeft,
/// child: Padding(
/// padding: const EdgeInsets.all(10.0),
/// child: Text('KITTEN', style: TextStyle(fontWeight: FontWeight.w900, color: Colors.white)),
/// ),
/// )
/// ),
/// ),
/// ),
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Container], a more generic form of this widget which paints itself,
/// rather that deferring to the nearest [Material] widget.
/// * [InkDecoration], the [InkFeature] subclass used by this widget to paint
/// on [Material] widgets.
/// * [InkWell] and [InkResponse], which also draw on [Material] widgets.
class Ink extends StatefulWidget {
/// Paints a decoration (which can be a simple color) on a [Material].
///
/// The [height] and [width] values include the [padding].
///
/// The `color` argument is a shorthand for
/// `decoration: BoxDecoration(color: color)`, which means you cannot supply
/// both a `color` and a `decoration` argument. If you want to have both a
/// `color` and a `decoration`, you can pass the color as the `color`
/// argument to the `BoxDecoration`.
///
/// If there is no intention to render anything on this decoration, consider
/// using a [Container] with a [BoxDecoration] instead.
Ink({
Key key,
this.padding,
Color color,
Decoration decoration,
this.width,
this.height,
this.child,
}) : assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".'
),
decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
super(key: key);
/// Creates a widget that shows an image (obtained from an [ImageProvider]) on
/// a [Material].
///
/// This argument is a shorthand for passing a [BoxDecoration] that has only
/// its [BoxDecoration.image] property set to the [Ink] constructor. The
/// properties of the [DecorationImage] of that [BoxDecoration] are set
/// according to the arguments passed to this method.
///
/// The `image` argument must not be null. If there is no
/// intention to render anything on this image, consider using a
/// [Container] with a [BoxDecoration.image] instead.
///
/// The `alignment`, `repeat`, and `matchTextDirection` arguments must not
/// be null either, but they have default values.
///
/// See [paintImage] for a description of the meaning of these arguments.
Ink.image({
Key key,
this.padding,
@required ImageProvider image,
ColorFilter colorFilter,
BoxFit fit,
AlignmentGeometry alignment = Alignment.center,
Rect centerSlice,
ImageRepeat repeat = ImageRepeat.noRepeat,
bool matchTextDirection = false,
this.width,
this.height,
this.child,
}) : assert(padding == null || padding.isNonNegative),
assert(image != null),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null),
decoration = BoxDecoration(
image: DecorationImage(
image: image,
colorFilter: colorFilter,
fit: fit,
alignment: alignment,
centerSlice: centerSlice,
repeat: repeat,
matchTextDirection: matchTextDirection,
),
),
super(key: key);
/// The [child] contained by the container.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// Empty space to inscribe inside the [decoration]. The [child], if any, is
/// placed inside this padding.
///
/// This padding is in addition to any padding inherent in the [decoration];
/// see [Decoration.padding].
final EdgeInsetsGeometry padding;
/// The decoration to paint on the nearest ancestor [Material] widget.
///
/// A shorthand for specifying just a solid color is available in the
/// constructor: set the `color` argument instead of the `decoration`
/// argument.
///
/// A shorthand for specifying just an image is also available using the
/// [Ink.image] constructor.
final Decoration decoration;
/// A width to apply to the [decoration] and the [child]. The width includes
/// any [padding].
final double width;
/// A height to apply to the [decoration] and the [child]. The height includes
/// any [padding].
final double height;
EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null)
return padding;
final EdgeInsetsGeometry decorationPadding = decoration.padding;
if (padding == null)
return decorationPadding;
return padding.add(decorationPadding);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
}
@override
_InkState createState() => _InkState();
}
class _InkState extends State<Ink> {
InkDecoration _ink;
void _handleRemoved() {
_ink = null;
}
@override
void deactivate() {
_ink?.dispose();
assert(_ink == null);
super.deactivate();
}
Widget _build(BuildContext context, BoxConstraints constraints) {
if (_ink == null) {
_ink = InkDecoration(
decoration: widget.decoration,
configuration: createLocalImageConfiguration(context),
controller: Material.of(context),
referenceBox: context.findRenderObject() as RenderBox,
onRemoved: _handleRemoved,
);
} else {
_ink.decoration = widget.decoration;
_ink.configuration = createLocalImageConfiguration(context);
}
Widget current = widget.child;
final EdgeInsetsGeometry effectivePadding = widget._paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
return current ?? Container();
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
Widget result = LayoutBuilder(
builder: _build,
);
if (widget.width != null || widget.height != null) {
result = SizedBox(
width: widget.width,
height: widget.height,
child: result,
);
}
return result;
}
}
/// A decoration on a part of a [Material].
///
/// This object is rarely created directly. Instead of creating an ink
/// decoration directly, consider using an [Ink] widget, which uses this class
/// in combination with [Padding] and [ConstrainedBox] to draw a decoration on a
/// [Material].
///
/// See also:
///
/// * [Ink], the corresponding widget.
/// * [InkResponse], which uses gestures to trigger ink highlights and ink
/// splashes in the parent [Material].
/// * [InkWell], which is a rectangular [InkResponse] (the most common type of
/// ink response).
/// * [Material], which is the widget on which the ink is painted.
class InkDecoration extends InkFeature {
/// Draws a decoration on a [Material].
InkDecoration({
@required Decoration decoration,
@required ImageConfiguration configuration,
@required MaterialInkController controller,
@required RenderBox referenceBox,
VoidCallback onRemoved,
}) : assert(configuration != null),
_configuration = configuration,
super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
this.decoration = decoration;
controller.addInkFeature(this);
}
BoxPainter _painter;
/// What to paint on the [Material].
///
/// The decoration is painted at the position and size of the [referenceBox],
/// on the [Material] that owns the [controller].
Decoration get decoration => _decoration;
Decoration _decoration;
set decoration(Decoration value) {
if (value == _decoration)
return;
_decoration = value;
_painter?.dispose();
_painter = _decoration?.createBoxPainter(_handleChanged);
controller.markNeedsPaint();
}
/// The configuration to pass to the [BoxPainter] obtained from the
/// [decoration], when painting.
///
/// The [ImageConfiguration.size] field is ignored (and replaced by the size
/// of the [referenceBox], at paint time).
ImageConfiguration get configuration => _configuration;
ImageConfiguration _configuration;
set configuration(ImageConfiguration value) {
assert(value != null);
if (value == _configuration)
return;
_configuration = value;
controller.markNeedsPaint();
}
void _handleChanged() {
controller.markNeedsPaint();
}
@override
void dispose() {
_painter?.dispose();
super.dispose();
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
if (_painter == null)
return;
final Offset originOffset = MatrixUtils.getAsTranslation(transform);
final ImageConfiguration sizedConfiguration = configuration.copyWith(
size: referenceBox.size,
);
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
_painter.paint(canvas, Offset.zero, sizedConfiguration);
canvas.restore();
} else {
_painter.paint(canvas, originOffset, sizedConfiguration);
}
}
}