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/// @docImport 'package:flutter/material.dart';
6///
7/// @docImport 'box_decoration.dart';
8library;
9
10import 'dart:collection';
11import 'dart:math' as math;
12import 'dart:ui' as ui show Gradient, lerpDouble;
13
14import 'package:flutter/foundation.dart';
15import 'package:vector_math/vector_math_64.dart';
16
17import 'alignment.dart';
18import 'basic_types.dart';
19
20class _ColorsAndStops {
21 _ColorsAndStops(this.colors, this.stops);
22 final List<Color> colors;
23 final List<double> stops;
24}
25
26/// Calculate the color at position [t] of the gradient defined by [colors] and [stops].
27Color _sample(List<Color> colors, List<double> stops, double t) {
28 assert(colors.isNotEmpty);
29 assert(stops.isNotEmpty);
30 if (t <= stops.first) {
31 return colors.first;
32 }
33 if (t >= stops.last) {
34 return colors.last;
35 }
36 final int index = stops.lastIndexWhere((double s) => s <= t);
37 assert(index != -1);
38 return Color.lerp(
39 colors[index],
40 colors[index + 1],
41 (t - stops[index]) / (stops[index + 1] - stops[index]),
42 )!;
43}
44
45_ColorsAndStops _interpolateColorsAndStops(
46 List<Color> aColors,
47 List<double> aStops,
48 List<Color> bColors,
49 List<double> bStops,
50 double t,
51) {
52 assert(aColors.length >= 2);
53 assert(bColors.length >= 2);
54 assert(aStops.length == aColors.length);
55 assert(bStops.length == bColors.length);
56 final SplayTreeSet<double> stops =
57 SplayTreeSet<double>()
58 ..addAll(aStops)
59 ..addAll(bStops);
60 final List<double> interpolatedStops = stops.toList(growable: false);
61 final List<Color> interpolatedColors = interpolatedStops
62 .map<Color>(
63 (double stop) =>
64 Color.lerp(_sample(aColors, aStops, stop), _sample(bColors, bStops, stop), t)!,
65 )
66 .toList(growable: false);
67 return _ColorsAndStops(interpolatedColors, interpolatedStops);
68}
69
70/// Base class for transforming gradient shaders without applying the same
71/// transform to the entire canvas.
72///
73/// For example, a [SweepGradient] normally starts its gradation at 3 o'clock
74/// and draws clockwise. To have the sweep appear to start at 6 o'clock, supply
75/// a [GradientRotation] of `pi/4` radians (i.e. 45 degrees).
76@immutable
77abstract class GradientTransform {
78 /// Abstract const constructor. This constructor enables subclasses to provide
79 /// const constructors so that they can be used in const expressions.
80 const GradientTransform();
81
82 /// When a [Gradient] creates its [Shader], it will call this method to
83 /// determine what transform to apply to the shader for the given [Rect] and
84 /// [TextDirection].
85 ///
86 /// Implementers may return null from this method, which achieves the same
87 /// final effect as returning [Matrix4.identity].
88 Matrix4? transform(Rect bounds, {TextDirection? textDirection});
89}
90
91/// A [GradientTransform] that rotates the gradient around the center-point of
92/// its bounding box.
93///
94/// {@tool snippet}
95///
96/// This sample would rotate a sweep gradient by a quarter turn clockwise:
97///
98/// ```dart
99/// const SweepGradient gradient = SweepGradient(
100/// colors: <Color>[Color(0xFFFFFFFF), Color(0xFF009900)],
101/// transform: GradientRotation(math.pi/4),
102/// );
103/// ```
104/// {@end-tool}
105@immutable
106class GradientRotation extends GradientTransform {
107 /// Constructs a [GradientRotation] for the specified angle.
108 ///
109 /// The angle is in radians in the clockwise direction.
110 const GradientRotation(this.radians);
111
112 /// The angle of rotation in radians in the clockwise direction.
113 final double radians;
114
115 @override
116 Matrix4 transform(Rect bounds, {TextDirection? textDirection}) {
117 final double sinRadians = math.sin(radians);
118 final double oneMinusCosRadians = 1 - math.cos(radians);
119 final Offset center = bounds.center;
120 final double originX = sinRadians * center.dy + oneMinusCosRadians * center.dx;
121 final double originY = -sinRadians * center.dx + oneMinusCosRadians * center.dy;
122
123 return Matrix4.identity()
124 ..translate(originX, originY)
125 ..rotateZ(radians);
126 }
127
128 @override
129 bool operator ==(Object other) {
130 if (identical(this, other)) {
131 return true;
132 }
133 if (other.runtimeType != runtimeType) {
134 return false;
135 }
136 return other is GradientRotation && other.radians == radians;
137 }
138
139 @override
140 int get hashCode => radians.hashCode;
141
142 @override
143 String toString() {
144 return '${objectRuntimeType(this, 'GradientRotation')}(radians: ${debugFormatDouble(radians)})';
145 }
146}
147
148/// A 2D gradient.
149///
150/// This is an interface that allows [LinearGradient], [RadialGradient], and
151/// [SweepGradient] classes to be used interchangeably in [BoxDecoration]s.
152///
153/// See also:
154///
155/// * [Gradient](dart-ui/Gradient-class.html), the class in the [dart:ui] library.
156///
157@immutable
158abstract class Gradient {
159 /// Initialize the gradient's colors and stops.
160 ///
161 /// The [colors] argument must have at least two colors (the length is not
162 /// verified until the [createShader] method is called).
163 ///
164 /// If specified, the [stops] argument must have the same number of entries as
165 /// [colors] (this is also not verified until the [createShader] method is
166 /// called).
167 ///
168 /// The [transform] argument can be applied to transform _only_ the gradient,
169 /// without rotating the canvas itself or other geometry on the canvas. For
170 /// example, a `GradientRotation(math.pi/4)` will result in a [SweepGradient]
171 /// that starts from a position of 6 o'clock instead of 3 o'clock, assuming
172 /// no other rotation or perspective transformations have been applied to the
173 /// [Canvas]. If null, no transformation is applied.
174 const Gradient({required this.colors, this.stops, this.transform});
175
176 /// The colors the gradient should obtain at each of the stops.
177 ///
178 /// If [stops] is non-null, this list must have the same length as [stops].
179 ///
180 /// This list must have at least two colors in it (otherwise, it's not a
181 /// gradient!).
182 final List<Color> colors;
183
184 /// A list of values from 0.0 to 1.0 that denote fractions along the gradient.
185 ///
186 /// If non-null, this list must have the same length as [colors].
187 ///
188 /// If the first value is not 0.0, then a stop with position 0.0 and a color
189 /// equal to the first color in [colors] is implied.
190 ///
191 /// If the last value is not 1.0, then a stop with position 1.0 and a color
192 /// equal to the last color in [colors] is implied.
193 ///
194 /// The values in the [stops] list must be in ascending order. If a value in
195 /// the [stops] list is less than an earlier value in the list, then its value
196 /// is assumed to equal the previous value.
197 ///
198 /// If stops is null, then a set of uniformly distributed stops is implied,
199 /// with the first stop at 0.0 and the last stop at 1.0.
200 final List<double>? stops;
201
202 /// The transform, if any, to apply to the gradient.
203 ///
204 /// This transform is in addition to any other transformations applied to the
205 /// canvas, but does not add any transformations to the canvas.
206 final GradientTransform? transform;
207
208 List<double> _impliedStops() {
209 if (stops != null) {
210 return stops!;
211 }
212 assert(colors.length >= 2, 'colors list must have at least two colors');
213 final double separation = 1.0 / (colors.length - 1);
214 return List<double>.generate(colors.length, (int index) => index * separation, growable: false);
215 }
216
217 /// Creates a [Shader] for this gradient to fill the given rect.
218 ///
219 /// If the gradient's configuration is text-direction-dependent, for example
220 /// it uses [AlignmentDirectional] objects instead of [Alignment]
221 /// objects, then the `textDirection` argument must not be null.
222 ///
223 /// The shader's transform will be resolved from the [transform] of this
224 /// gradient.
225 @factory
226 Shader createShader(Rect rect, {TextDirection? textDirection});
227
228 /// Returns a new gradient with its properties scaled by the given factor.
229 ///
230 /// A factor of 0.0 (or less) should result in a variant of the gradient that
231 /// is invisible; any two factors epsilon apart should be unnoticeably
232 /// different from each other at first glance. From this it follows that
233 /// scaling a gradient with values from 1.0 to 0.0 over time should cause the
234 /// gradient to smoothly disappear.
235 ///
236 /// Typically this is the same as interpolating from null (with [lerp]).
237 Gradient scale(double factor);
238
239 /// Returns a new [Gradient] with each color set to the given opacity.
240 Gradient withOpacity(double opacity);
241
242 /// Linearly interpolates from another [Gradient] to `this`.
243 ///
244 /// When implementing this method in subclasses, return null if this class
245 /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo]
246 /// method instead.
247 ///
248 /// If `a` is null, this must not return null. The base class implements this
249 /// by deferring to [scale].
250 ///
251 /// The `t` argument represents position on the timeline, with 0.0 meaning
252 /// that the interpolation has not started, returning `a` (or something
253 /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
254 /// returning `this` (or something equivalent to `this`), and values in
255 /// between meaning that the interpolation is at the relevant point on the
256 /// timeline between `a` and `this`. The interpolation can be extrapolated
257 /// beyond 0.0 and 1.0, so negative values and values greater than 1.0 are
258 /// valid (and can easily be generated by curves such as
259 /// [Curves.elasticInOut]).
260 ///
261 /// Values for `t` are usually obtained from an [Animation<double>], such as
262 /// an [AnimationController].
263 ///
264 /// Instead of calling this directly, use [Gradient.lerp].
265 @protected
266 Gradient? lerpFrom(Gradient? a, double t) {
267 if (a == null) {
268 return scale(t);
269 }
270 return null;
271 }
272
273 /// Linearly interpolates from `this` to another [Gradient].
274 ///
275 /// This is called if `b`'s [lerpTo] did not know how to handle this class.
276 ///
277 /// When implementing this method in subclasses, return null if this class
278 /// cannot interpolate from `b`. In that case, [lerp] will apply a default
279 /// behavior instead.
280 ///
281 /// If `b` is null, this must not return null. The base class implements this
282 /// by deferring to [scale].
283 ///
284 /// The `t` argument represents position on the timeline, with 0.0 meaning
285 /// that the interpolation has not started, returning `this` (or something
286 /// equivalent to `this`), 1.0 meaning that the interpolation has finished,
287 /// returning `b` (or something equivalent to `b`), and values in between
288 /// meaning that the interpolation is at the relevant point on the timeline
289 /// between `this` and `b`. The interpolation can be extrapolated beyond 0.0
290 /// and 1.0, so negative values and values greater than 1.0 are valid (and can
291 /// easily be generated by curves such as [Curves.elasticInOut]).
292 ///
293 /// Values for `t` are usually obtained from an [Animation<double>], such as
294 /// an [AnimationController].
295 ///
296 /// Instead of calling this directly, use [Gradient.lerp].
297 @protected
298 Gradient? lerpTo(Gradient? b, double t) {
299 if (b == null) {
300 return scale(1.0 - t);
301 }
302 return null;
303 }
304
305 /// Linearly interpolates between two [Gradient]s.
306 ///
307 /// This defers to `b`'s [lerpTo] function if `b` is not null. If `b` is
308 /// null or if its [lerpTo] returns null, it uses `a`'s [lerpFrom]
309 /// function instead. If both return null, it returns `a` before `t == 0.5`
310 /// and `b` after `t == 0.5`.
311 ///
312 /// {@macro dart.ui.shadow.lerp}
313 static Gradient? lerp(Gradient? a, Gradient? b, double t) {
314 if (identical(a, b)) {
315 return a;
316 }
317 Gradient? result;
318 if (b != null) {
319 result = b.lerpFrom(a, t); // if a is null, this must return non-null
320 }
321 if (result == null && a != null) {
322 result = a.lerpTo(b, t); // if b is null, this must return non-null
323 }
324 if (result != null) {
325 return result;
326 }
327 assert(a != null && b != null);
328 return t < 0.5 ? a!.scale(1.0 - (t * 2.0)) : b!.scale((t - 0.5) * 2.0);
329 }
330
331 Float64List? _resolveTransform(Rect bounds, TextDirection? textDirection) {
332 return transform?.transform(bounds, textDirection: textDirection)?.storage;
333 }
334}
335
336/// A 2D linear gradient.
337///
338/// {@youtube 560 315 https://www.youtube.com/watch?v=gYNTcgZVcWw}
339///
340/// This class is used by [BoxDecoration] to represent linear gradients. This
341/// abstracts out the arguments to the [ui.Gradient.linear] constructor from
342/// the `dart:ui` library.
343///
344/// A gradient has two anchor points, [begin] and [end]. The [begin] point
345/// corresponds to 0.0, and the [end] point corresponds to 1.0. These points are
346/// expressed in fractions, so that the same gradient can be reused with varying
347/// sized boxes without changing the parameters. (This contrasts with [
348/// ui.Gradient.linear], whose arguments are expressed in logical pixels.)
349///
350/// The [colors] are described by a list of [Color] objects. There must be at
351/// least two colors. The [stops] list, if specified, must have the same length
352/// as [colors]. It specifies fractions of the vector from start to end, between
353/// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
354/// assumed.
355///
356/// The region of the canvas before [begin] and after [end] is colored according
357/// to [tileMode].
358///
359/// Typically this class is used with [BoxDecoration], which does the painting.
360/// To use a [LinearGradient] to paint on a canvas directly, see [createShader].
361///
362/// {@tool dartpad}
363/// This sample draws a picture with a gradient sweeping through different
364/// colors, by having a [Container] display a [BoxDecoration] with a
365/// [LinearGradient].
366///
367/// ** See code in examples/api/lib/painting/gradient/linear_gradient.0.dart **
368/// {@end-tool}
369///
370/// See also:
371///
372/// * [RadialGradient], which displays a gradient in concentric circles, and
373/// has an example which shows a different way to use [Gradient] objects.
374/// * [SweepGradient], which displays a gradient in a sweeping arc around a
375/// center point.
376/// * [BoxDecoration], which can take a [LinearGradient] in its
377/// [BoxDecoration.gradient] property.
378class LinearGradient extends Gradient {
379 /// Creates a linear gradient.
380 ///
381 /// If [stops] is non-null, it must have the same length as [colors].
382 const LinearGradient({
383 this.begin = Alignment.centerLeft,
384 this.end = Alignment.centerRight,
385 required super.colors,
386 super.stops,
387 this.tileMode = TileMode.clamp,
388 super.transform,
389 });
390
391 /// The offset at which stop 0.0 of the gradient is placed.
392 ///
393 /// If this is an [Alignment], then it is expressed as a vector from
394 /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
395 /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
396 ///
397 /// For example, a begin offset of (-1.0, 0.0) is half way down the
398 /// left side of the box.
399 ///
400 /// It can also be an [AlignmentDirectional], where the start is the
401 /// left in left-to-right contexts and the right in right-to-left contexts. If
402 /// a text-direction-dependent value is provided here, then the [createShader]
403 /// method will need to be given a [TextDirection].
404 final AlignmentGeometry begin;
405
406 /// The offset at which stop 1.0 of the gradient is placed.
407 ///
408 /// If this is an [Alignment], then it is expressed as a vector from
409 /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
410 /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
411 ///
412 /// For example, a begin offset of (1.0, 0.0) is half way down the
413 /// right side of the box.
414 ///
415 /// It can also be an [AlignmentDirectional], where the start is the left in
416 /// left-to-right contexts and the right in right-to-left contexts. If a
417 /// text-direction-dependent value is provided here, then the [createShader]
418 /// method will need to be given a [TextDirection].
419 final AlignmentGeometry end;
420
421 /// How this gradient should tile the plane beyond in the region before
422 /// [begin] and after [end].
423 ///
424 /// For details, see [TileMode].
425 ///
426 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
427 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_decal_linear.png)
428 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
429 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_linear.png)
430 final TileMode tileMode;
431
432 @override
433 Shader createShader(Rect rect, {TextDirection? textDirection}) {
434 return ui.Gradient.linear(
435 begin.resolve(textDirection).withinRect(rect),
436 end.resolve(textDirection).withinRect(rect),
437 colors,
438 _impliedStops(),
439 tileMode,
440 _resolveTransform(rect, textDirection),
441 );
442 }
443
444 /// Returns a new [LinearGradient] with its colors scaled by the given factor.
445 ///
446 /// Since the alpha component of the Color is what is scaled, a factor
447 /// of 0.0 or less results in a gradient that is fully transparent.
448 @override
449 LinearGradient scale(double factor) {
450 return LinearGradient(
451 begin: begin,
452 end: end,
453 colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
454 stops: stops,
455 tileMode: tileMode,
456 );
457 }
458
459 @override
460 Gradient? lerpFrom(Gradient? a, double t) {
461 if (a is LinearGradient?) {
462 return LinearGradient.lerp(a, this, t);
463 }
464 return super.lerpFrom(a, t);
465 }
466
467 @override
468 Gradient? lerpTo(Gradient? b, double t) {
469 if (b is LinearGradient?) {
470 return LinearGradient.lerp(this, b, t);
471 }
472 return super.lerpTo(b, t);
473 }
474
475 /// Linearly interpolate between two [LinearGradient]s.
476 ///
477 /// If either gradient is null, this function linearly interpolates from
478 /// a gradient that matches the other gradient in [begin], [end], [stops] and
479 /// [tileMode] and with the same [colors] but transparent (using [scale]).
480 ///
481 /// If neither gradient is null, they must have the same number of [colors].
482 ///
483 /// The `t` argument represents a position on the timeline, with 0.0 meaning
484 /// that the interpolation has not started, returning `a` (or something
485 /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
486 /// returning `b` (or something equivalent to `b`), and values in between
487 /// meaning that the interpolation is at the relevant point on the timeline
488 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
489 /// 1.0, so negative values and values greater than 1.0 are valid (and can
490 /// easily be generated by curves such as [Curves.elasticInOut]).
491 ///
492 /// Values for `t` are usually obtained from an [Animation<double>], such as
493 /// an [AnimationController].
494 static LinearGradient? lerp(LinearGradient? a, LinearGradient? b, double t) {
495 if (identical(a, b)) {
496 return a;
497 }
498 if (a == null) {
499 return b!.scale(t);
500 }
501 if (b == null) {
502 return a.scale(1.0 - t);
503 }
504 final _ColorsAndStops interpolated = _interpolateColorsAndStops(
505 a.colors,
506 a._impliedStops(),
507 b.colors,
508 b._impliedStops(),
509 t,
510 );
511 return LinearGradient(
512 begin: AlignmentGeometry.lerp(a.begin, b.begin, t)!,
513 end: AlignmentGeometry.lerp(a.end, b.end, t)!,
514 colors: interpolated.colors,
515 stops: interpolated.stops,
516 tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
517 transform: t < 0.5 ? a.transform : b.transform,
518 );
519 }
520
521 @override
522 bool operator ==(Object other) {
523 if (identical(this, other)) {
524 return true;
525 }
526 if (other.runtimeType != runtimeType) {
527 return false;
528 }
529 return other is LinearGradient &&
530 other.begin == begin &&
531 other.end == end &&
532 other.tileMode == tileMode &&
533 other.transform == transform &&
534 listEquals<Color>(other.colors, colors) &&
535 listEquals<double>(other.stops, stops);
536 }
537
538 @override
539 int get hashCode => Object.hash(
540 begin,
541 end,
542 tileMode,
543 transform,
544 Object.hashAll(colors),
545 stops == null ? null : Object.hashAll(stops!),
546 );
547
548 @override
549 String toString() {
550 final List<String> description = <String>[
551 'begin: $begin',
552 'end: $end',
553 'colors: $colors',
554 if (stops != null) 'stops: $stops',
555 'tileMode: $tileMode',
556 if (transform != null) 'transform: $transform',
557 ];
558
559 return '${objectRuntimeType(this, 'LinearGradient')}(${description.join(', ')})';
560 }
561
562 @override
563 LinearGradient withOpacity(double opacity) {
564 return LinearGradient(
565 begin: begin,
566 end: end,
567 colors: <Color>[for (final Color color in colors) color.withOpacity(opacity)],
568 stops: stops,
569 tileMode: tileMode,
570 transform: transform,
571 );
572 }
573}
574
575/// A 2D radial gradient.
576///
577/// This class is used by [BoxDecoration] to represent radial gradients. This
578/// abstracts out the arguments to the [ui.Gradient.radial] constructor from
579/// the `dart:ui` library.
580///
581/// A normal radial gradient has a [center] and a [radius]. The [center] point
582/// corresponds to 0.0, and the ring at [radius] from the center corresponds
583/// to 1.0. These lengths are expressed in fractions, so that the same gradient
584/// can be reused with varying sized boxes without changing the parameters.
585/// (This contrasts with [ui.Gradient.radial], whose arguments are expressed
586/// in logical pixels.)
587///
588/// It is also possible to create a two-point (or focal pointed) radial gradient
589/// (which is sometimes referred to as a two point conic gradient, but is not the
590/// same as a CSS conic gradient which corresponds to a [SweepGradient]). A [focal]
591/// point and [focalRadius] can be specified similarly to [center] and [radius],
592/// which will make the rendered gradient appear to be pointed or directed in the
593/// direction of the [focal] point. This is only important if [focal] and [center]
594/// are not equal or [focalRadius] > 0.0 (as this case is visually identical to a
595/// normal radial gradient). One important case to avoid is having [focal] and
596/// [center] both resolve to [Offset.zero] when [focalRadius] > 0.0. In such a case,
597/// a valid shader cannot be created by the framework.
598///
599/// The [colors] are described by a list of [Color] objects. There must be at
600/// least two colors. The [stops] list, if specified, must have the same length
601/// as [colors]. It specifies fractions of the radius between 0.0 and 1.0,
602/// giving concentric rings for each color stop. If it is null, a uniform
603/// distribution is assumed.
604///
605/// The region of the canvas beyond [radius] from the [center] is colored
606/// according to [tileMode].
607///
608/// Typically this class is used with [BoxDecoration], which does the painting.
609/// To use a [RadialGradient] to paint on a canvas directly, see [createShader].
610///
611/// {@tool snippet}
612///
613/// This function draws a gradient that looks like a sun in a blue sky.
614///
615/// ```dart
616/// void paintSky(Canvas canvas, Rect rect) {
617/// const RadialGradient gradient = RadialGradient(
618/// center: Alignment(0.7, -0.6), // near the top right
619/// radius: 0.2,
620/// colors: <Color>[
621/// Color(0xFFFFFF00), // yellow sun
622/// Color(0xFF0099FF), // blue sky
623/// ],
624/// stops: <double>[0.4, 1.0],
625/// );
626/// // rect is the area we are painting over
627/// final Paint paint = Paint()
628/// ..shader = gradient.createShader(rect);
629/// canvas.drawRect(rect, paint);
630/// }
631/// ```
632/// {@end-tool}
633///
634/// See also:
635///
636/// * [LinearGradient], which displays a gradient in parallel lines, and has an
637/// example which shows a different way to use [Gradient] objects.
638/// * [SweepGradient], which displays a gradient in a sweeping arc around a
639/// center point.
640/// * [BoxDecoration], which can take a [RadialGradient] in its
641/// [BoxDecoration.gradient] property.
642/// * [CustomPainter], which shows how to use the above sample code in a custom
643/// painter.
644class RadialGradient extends Gradient {
645 /// Creates a radial gradient.
646 ///
647 /// If [stops] is non-null, it must have the same length as [colors].
648 const RadialGradient({
649 this.center = Alignment.center,
650 this.radius = 0.5,
651 required super.colors,
652 super.stops,
653 this.tileMode = TileMode.clamp,
654 this.focal,
655 this.focalRadius = 0.0,
656 super.transform,
657 });
658
659 /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
660 /// square describing the gradient which will be mapped onto the paint box.
661 ///
662 /// For example, an alignment of (0.0, 0.0) will place the radial
663 /// gradient in the center of the box.
664 ///
665 /// If this is an [Alignment], then it is expressed as a vector from
666 /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
667 /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
668 ///
669 /// It can also be an [AlignmentDirectional], where the start is the left in
670 /// left-to-right contexts and the right in right-to-left contexts. If a
671 /// text-direction-dependent value is provided here, then the [createShader]
672 /// method will need to be given a [TextDirection].
673 final AlignmentGeometry center;
674
675 /// The radius of the gradient, as a fraction of the shortest side
676 /// of the paint box.
677 ///
678 /// For example, if a radial gradient is painted on a box that is
679 /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
680 /// will place the 1.0 stop at 100.0 pixels from the [center].
681 final double radius;
682
683 /// How this gradient should tile the plane beyond the outer ring at [radius]
684 /// pixels from the [center].
685 ///
686 /// For details, see [TileMode].
687 ///
688 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
689 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_decal_radial.png)
690 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
691 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radial.png)
692 ///
693 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radialWithFocal.png)
694 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_decal_radialWithFocal.png)
695 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radialWithFocal.png)
696 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radialWithFocal.png)
697 final TileMode tileMode;
698
699 /// The focal point of the gradient. If specified, the gradient will appear
700 /// to be focused along the vector from [center] to focal.
701 ///
702 /// See [center] for a description of how the coordinates are mapped.
703 ///
704 /// If this value is specified and [focalRadius] > 0.0, care should be taken
705 /// to ensure that either this value or [center] will not both resolve to
706 /// [Offset.zero], which would fail to create a valid gradient.
707 final AlignmentGeometry? focal;
708
709 /// The radius of the focal point of gradient, as a fraction of the shortest
710 /// side of the paint box.
711 ///
712 /// For example, if a radial gradient is painted on a box that is
713 /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
714 /// will place the 1.0 stop at 100.0 pixels from the [focal] point.
715 ///
716 /// If this value is specified and is greater than 0.0, either [focal] or
717 /// [center] must not resolve to [Offset.zero], which would fail to create
718 /// a valid gradient.
719 final double focalRadius;
720
721 @override
722 Shader createShader(Rect rect, {TextDirection? textDirection}) {
723 return ui.Gradient.radial(
724 center.resolve(textDirection).withinRect(rect),
725 radius * rect.shortestSide,
726 colors,
727 _impliedStops(),
728 tileMode,
729 _resolveTransform(rect, textDirection),
730 focal?.resolve(textDirection).withinRect(rect),
731 focalRadius * rect.shortestSide,
732 );
733 }
734
735 /// Returns a new [RadialGradient] with its colors scaled by the given factor.
736 ///
737 /// Since the alpha component of the Color is what is scaled, a factor
738 /// of 0.0 or less results in a gradient that is fully transparent.
739 @override
740 RadialGradient scale(double factor) {
741 return RadialGradient(
742 center: center,
743 radius: radius,
744 colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
745 stops: stops,
746 tileMode: tileMode,
747 focal: focal,
748 focalRadius: focalRadius,
749 );
750 }
751
752 @override
753 Gradient? lerpFrom(Gradient? a, double t) {
754 if (a is RadialGradient?) {
755 return RadialGradient.lerp(a, this, t);
756 }
757 return super.lerpFrom(a, t);
758 }
759
760 @override
761 Gradient? lerpTo(Gradient? b, double t) {
762 if (b is RadialGradient?) {
763 return RadialGradient.lerp(this, b, t);
764 }
765 return super.lerpTo(b, t);
766 }
767
768 /// Linearly interpolate between two [RadialGradient]s.
769 ///
770 /// If either gradient is null, this function linearly interpolates from
771 /// a gradient that matches the other gradient in [center], [radius], [stops] and
772 /// [tileMode] and with the same [colors] but transparent (using [scale]).
773 ///
774 /// If neither gradient is null, they must have the same number of [colors].
775 ///
776 /// The `t` argument represents a position on the timeline, with 0.0 meaning
777 /// that the interpolation has not started, returning `a` (or something
778 /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
779 /// returning `b` (or something equivalent to `b`), and values in between
780 /// meaning that the interpolation is at the relevant point on the timeline
781 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
782 /// 1.0, so negative values and values greater than 1.0 are valid (and can
783 /// easily be generated by curves such as [Curves.elasticInOut]).
784 ///
785 /// Values for `t` are usually obtained from an [Animation<double>], such as
786 /// an [AnimationController].
787 static RadialGradient? lerp(RadialGradient? a, RadialGradient? b, double t) {
788 if (identical(a, b)) {
789 return a;
790 }
791 if (a == null) {
792 return b!.scale(t);
793 }
794 if (b == null) {
795 return a.scale(1.0 - t);
796 }
797 final _ColorsAndStops interpolated = _interpolateColorsAndStops(
798 a.colors,
799 a._impliedStops(),
800 b.colors,
801 b._impliedStops(),
802 t,
803 );
804 return RadialGradient(
805 center: AlignmentGeometry.lerp(a.center, b.center, t)!,
806 radius: math.max(0.0, ui.lerpDouble(a.radius, b.radius, t)!),
807 colors: interpolated.colors,
808 stops: interpolated.stops,
809 tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
810 focal: AlignmentGeometry.lerp(a.focal, b.focal, t),
811 focalRadius: math.max(0.0, ui.lerpDouble(a.focalRadius, b.focalRadius, t)!),
812 transform: t < 0.5 ? a.transform : b.transform,
813 );
814 }
815
816 @override
817 bool operator ==(Object other) {
818 if (identical(this, other)) {
819 return true;
820 }
821 if (other.runtimeType != runtimeType) {
822 return false;
823 }
824 return other is RadialGradient &&
825 other.center == center &&
826 other.radius == radius &&
827 other.tileMode == tileMode &&
828 other.transform == transform &&
829 listEquals<Color>(other.colors, colors) &&
830 listEquals<double>(other.stops, stops) &&
831 other.focal == focal &&
832 other.focalRadius == focalRadius;
833 }
834
835 @override
836 int get hashCode => Object.hash(
837 center,
838 radius,
839 tileMode,
840 transform,
841 Object.hashAll(colors),
842 stops == null ? null : Object.hashAll(stops!),
843 focal,
844 focalRadius,
845 );
846
847 @override
848 String toString() {
849 final List<String> description = <String>[
850 'center: $center',
851 'radius: ${debugFormatDouble(radius)}',
852 'colors: $colors',
853 if (stops != null) 'stops: $stops',
854 'tileMode: $tileMode',
855 if (focal != null) 'focal: $focal',
856 'focalRadius: ${debugFormatDouble(focalRadius)}',
857 if (transform != null) 'transform: $transform',
858 ];
859
860 return '${objectRuntimeType(this, 'RadialGradient')}(${description.join(', ')})';
861 }
862
863 @override
864 RadialGradient withOpacity(double opacity) {
865 return RadialGradient(
866 center: center,
867 radius: radius,
868 colors: <Color>[for (final Color color in colors) color.withOpacity(opacity)],
869 stops: stops,
870 tileMode: tileMode,
871 focal: focal,
872 focalRadius: focalRadius,
873 transform: transform,
874 );
875 }
876}
877
878/// A 2D sweep gradient.
879///
880/// This class is used by [BoxDecoration] to represent sweep gradients. This
881/// abstracts out the arguments to the [ui.Gradient.sweep] constructor from
882/// the `dart:ui` library.
883///
884/// A gradient has a [center], a [startAngle], and an [endAngle]. The [startAngle]
885/// corresponds to 0.0, and the [endAngle] corresponds to 1.0. These angles are
886/// expressed in radians.
887///
888/// The [colors] are described by a list of [Color] objects. There must be at
889/// least two colors. The [stops] list, if specified, must have the same length
890/// as [colors]. It specifies fractions of the vector from start to end, between
891/// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
892/// assumed.
893///
894/// The region of the canvas before [startAngle] and after [endAngle] is colored
895/// according to [tileMode].
896///
897/// Typically this class is used with [BoxDecoration], which does the painting.
898/// To use a [SweepGradient] to paint on a canvas directly, see [createShader].
899///
900/// {@tool snippet}
901///
902/// This sample draws a different color in each quadrant.
903///
904/// ```dart
905/// Container(
906/// decoration: const BoxDecoration(
907/// gradient: SweepGradient(
908/// center: FractionalOffset.center,
909/// colors: <Color>[
910/// Color(0xFF4285F4), // blue
911/// Color(0xFF34A853), // green
912/// Color(0xFFFBBC05), // yellow
913/// Color(0xFFEA4335), // red
914/// Color(0xFF4285F4), // blue again to seamlessly transition to the start
915/// ],
916/// stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
917/// ),
918/// )
919/// )
920/// ```
921/// {@end-tool}
922///
923/// {@tool snippet}
924///
925/// This sample takes the above gradient and rotates it by `math.pi/4` radians,
926/// i.e. 45 degrees.
927///
928/// ```dart
929/// Container(
930/// decoration: const BoxDecoration(
931/// gradient: SweepGradient(
932/// center: FractionalOffset.center,
933/// colors: <Color>[
934/// Color(0xFF4285F4), // blue
935/// Color(0xFF34A853), // green
936/// Color(0xFFFBBC05), // yellow
937/// Color(0xFFEA4335), // red
938/// Color(0xFF4285F4), // blue again to seamlessly transition to the start
939/// ],
940/// stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
941/// transform: GradientRotation(math.pi/4),
942/// ),
943/// ),
944/// )
945/// ```
946/// {@end-tool}
947///
948/// See also:
949///
950/// * [LinearGradient], which displays a gradient in parallel lines, and has an
951/// example which shows a different way to use [Gradient] objects.
952/// * [RadialGradient], which displays a gradient in concentric circles, and
953/// has an example which shows a different way to use [Gradient] objects.
954/// * [BoxDecoration], which can take a [SweepGradient] in its
955/// [BoxDecoration.gradient] property.
956class SweepGradient extends Gradient {
957 /// Creates a sweep gradient.
958 ///
959 /// If [stops] is non-null, it must have the same length as [colors].
960 const SweepGradient({
961 this.center = Alignment.center,
962 this.startAngle = 0.0,
963 this.endAngle = math.pi * 2,
964 required super.colors,
965 super.stops,
966 this.tileMode = TileMode.clamp,
967 super.transform,
968 });
969
970 /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
971 /// square describing the gradient which will be mapped onto the paint box.
972 ///
973 /// For example, an alignment of (0.0, 0.0) will place the sweep
974 /// gradient in the center of the box.
975 ///
976 /// If this is an [Alignment], then it is expressed as a vector from
977 /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
978 /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
979 ///
980 /// It can also be an [AlignmentDirectional], where the start is the left in
981 /// left-to-right contexts and the right in right-to-left contexts. If a
982 /// text-direction-dependent value is provided here, then the [createShader]
983 /// method will need to be given a [TextDirection].
984 final AlignmentGeometry center;
985
986 /// The angle in radians at which stop 0.0 of the gradient is placed.
987 ///
988 /// Defaults to 0.0.
989 final double startAngle;
990
991 /// The angle in radians at which stop 1.0 of the gradient is placed.
992 ///
993 /// Defaults to math.pi * 2.
994 final double endAngle;
995
996 /// How this gradient should tile the plane beyond in the region before
997 /// [startAngle] and after [endAngle].
998 ///
999 /// For details, see [TileMode].
1000 ///
1001 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_sweep.png)
1002 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_decal_sweep.png)
1003 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_sweep.png)
1004 /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_sweep.png)
1005 final TileMode tileMode;
1006
1007 @override
1008 Shader createShader(Rect rect, {TextDirection? textDirection}) {
1009 return ui.Gradient.sweep(
1010 center.resolve(textDirection).withinRect(rect),
1011 colors,
1012 _impliedStops(),
1013 tileMode,
1014 startAngle,
1015 endAngle,
1016 _resolveTransform(rect, textDirection),
1017 );
1018 }
1019
1020 /// Returns a new [SweepGradient] with its colors scaled by the given factor.
1021 ///
1022 /// Since the alpha component of the Color is what is scaled, a factor
1023 /// of 0.0 or less results in a gradient that is fully transparent.
1024 @override
1025 SweepGradient scale(double factor) {
1026 return SweepGradient(
1027 center: center,
1028 startAngle: startAngle,
1029 endAngle: endAngle,
1030 colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
1031 stops: stops,
1032 tileMode: tileMode,
1033 );
1034 }
1035
1036 @override
1037 Gradient? lerpFrom(Gradient? a, double t) {
1038 if (a is SweepGradient?) {
1039 return SweepGradient.lerp(a, this, t);
1040 }
1041 return super.lerpFrom(a, t);
1042 }
1043
1044 @override
1045 Gradient? lerpTo(Gradient? b, double t) {
1046 if (b is SweepGradient?) {
1047 return SweepGradient.lerp(this, b, t);
1048 }
1049 return super.lerpTo(b, t);
1050 }
1051
1052 /// Linearly interpolate between two [SweepGradient]s.
1053 ///
1054 /// If either gradient is null, then the non-null gradient is returned with
1055 /// its color scaled in the same way as the [scale] function.
1056 ///
1057 /// If neither gradient is null, they must have the same number of [colors].
1058 ///
1059 /// The `t` argument represents a position on the timeline, with 0.0 meaning
1060 /// that the interpolation has not started, returning `a` (or something
1061 /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
1062 /// returning `b` (or something equivalent to `b`), and values in between
1063 /// meaning that the interpolation is at the relevant point on the timeline
1064 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
1065 /// 1.0, so negative values and values greater than 1.0 are valid (and can
1066 /// easily be generated by curves such as [Curves.elasticInOut]).
1067 ///
1068 /// Values for `t` are usually obtained from an [Animation<double>], such as
1069 /// an [AnimationController].
1070 static SweepGradient? lerp(SweepGradient? a, SweepGradient? b, double t) {
1071 if (identical(a, b)) {
1072 return a;
1073 }
1074 if (a == null) {
1075 return b!.scale(t);
1076 }
1077 if (b == null) {
1078 return a.scale(1.0 - t);
1079 }
1080 final _ColorsAndStops interpolated = _interpolateColorsAndStops(
1081 a.colors,
1082 a._impliedStops(),
1083 b.colors,
1084 b._impliedStops(),
1085 t,
1086 );
1087 return SweepGradient(
1088 center: AlignmentGeometry.lerp(a.center, b.center, t)!,
1089 startAngle: math.max(0.0, ui.lerpDouble(a.startAngle, b.startAngle, t)!),
1090 endAngle: math.max(0.0, ui.lerpDouble(a.endAngle, b.endAngle, t)!),
1091 colors: interpolated.colors,
1092 stops: interpolated.stops,
1093 tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
1094 transform: t < 0.5 ? a.transform : b.transform,
1095 );
1096 }
1097
1098 @override
1099 bool operator ==(Object other) {
1100 if (identical(this, other)) {
1101 return true;
1102 }
1103 if (other.runtimeType != runtimeType) {
1104 return false;
1105 }
1106 return other is SweepGradient &&
1107 other.center == center &&
1108 other.startAngle == startAngle &&
1109 other.endAngle == endAngle &&
1110 other.tileMode == tileMode &&
1111 other.transform == transform &&
1112 listEquals<Color>(other.colors, colors) &&
1113 listEquals<double>(other.stops, stops);
1114 }
1115
1116 @override
1117 int get hashCode => Object.hash(
1118 center,
1119 startAngle,
1120 endAngle,
1121 tileMode,
1122 transform,
1123 Object.hashAll(colors),
1124 stops == null ? null : Object.hashAll(stops!),
1125 );
1126
1127 @override
1128 String toString() {
1129 final List<String> description = <String>[
1130 'center: $center',
1131 'startAngle: ${debugFormatDouble(startAngle)}',
1132 'endAngle: ${debugFormatDouble(endAngle)}',
1133 'colors: $colors',
1134 if (stops != null) 'stops: $stops',
1135 'tileMode: $tileMode',
1136 if (transform != null) 'transform: $transform',
1137 ];
1138
1139 return '${objectRuntimeType(this, 'SweepGradient')}(${description.join(', ')})';
1140 }
1141
1142 @override
1143 SweepGradient withOpacity(double opacity) {
1144 return SweepGradient(
1145 center: center,
1146 startAngle: startAngle,
1147 endAngle: endAngle,
1148 colors: <Color>[for (final Color color in colors) color.withOpacity(opacity)],
1149 stops: stops,
1150 tileMode: tileMode,
1151 transform: transform,
1152 );
1153 }
1154}
1155

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com