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'; |
8 | library; |
9 | |
10 | import 'dart:collection'; |
11 | import 'dart:math' as math; |
12 | import 'dart:ui' as ui show Gradient, lerpDouble; |
13 | |
14 | import 'package:flutter/foundation.dart'; |
15 | import 'package:vector_math/vector_math_64.dart'; |
16 | |
17 | import 'alignment.dart'; |
18 | import 'basic_types.dart'; |
19 | |
20 | class _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]. |
27 | Color _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 |
77 | abstract 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 |
106 | class 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 |
158 | abstract 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. |
378 | class 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 | ///  |
427 | ///  |
428 | ///  |
429 | ///  |
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. |
644 | class 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 | ///  |
689 | ///  |
690 | ///  |
691 | ///  |
692 | /// |
693 | ///  |
694 | ///  |
695 | ///  |
696 | ///  |
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. |
956 | class 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 | ///  |
1002 | ///  |
1003 | ///  |
1004 | ///  |
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 |
Definitions
- _ColorsAndStops
- _ColorsAndStops
- _sample
- _interpolateColorsAndStops
- GradientTransform
- GradientTransform
- transform
- GradientRotation
- GradientRotation
- transform
- ==
- hashCode
- toString
- Gradient
- Gradient
- _impliedStops
- createShader
- scale
- withOpacity
- lerpFrom
- lerpTo
- lerp
- _resolveTransform
- LinearGradient
- LinearGradient
- createShader
- scale
- lerpFrom
- lerpTo
- lerp
- ==
- hashCode
- toString
- withOpacity
- RadialGradient
- RadialGradient
- createShader
- scale
- lerpFrom
- lerpTo
- lerp
- ==
- hashCode
- toString
- withOpacity
- SweepGradient
- SweepGradient
- createShader
- scale
- lerpFrom
- lerpTo
- lerp
- ==
- hashCode
- toString
Learn more about Flutter for embedded and desktop on industrialflutter.com