InkSparkle constructor Null safety
- {required MaterialInkController controller,
- required RenderBox referenceBox,
- required Color color,
- required Offset position,
- required TextDirection textDirection,
- bool containedInkWell = true,
- RectCallback? rectCallback,
- BorderRadius? borderRadius,
- ShapeBorder? customBorder,
- double? radius,
- VoidCallback? onRemoved,
- double? turbulenceSeed}
Begin a sparkly ripple effect, centered at position relative to
referenceBox.
The color defines the color of the splash itself. The sparkles are
always white.
The controller argument is typically obtained via
Material.of(context).
textDirection is used by customBorder if it is non-null. This allows
the customBorder's path to be properly defined if it was the path was
expressed in terms of "start" and "end" instead of
"left" and "right".
If containedInkWell is true, then the ripple will be sized to fit
the well rectangle, then clipped to it when drawn. The well
rectangle is the box returned by rectCallback, if provided, or
otherwise is the bounds of the referenceBox.
If containedInkWell is false, then rectCallback should be null.
The ink ripple is clipped only to the edges of the Material.
This is the default.
Clipping can happen in 3 different ways:
- If
customBorderis provided, it is used to determine the path for clipping. - If
customBorderis null, andborderRadiusis provided, then the canvas is clipped by an RRect created fromborderRadius. - If
borderRadiusis the default BorderRadius.zero, then the canvas is clipped withrectCallback. When the ripple is removed, onRemoved will be called.
turbulenceSeed can be passed if a non random seed should be used for
the turbulence and sparkles. By default, the seed is a random number
between 0.0 and 1000.0.
Turbulence is an input to the shader and helps to provides a more natural, non-circular, "splash" effect.
Sparkle randomization is also driven by the turbulenceSeed. Sparkles are
identified in the shader as "noise", and the sparkles are derived from
pseudorandom triangular noise.
Implementation
InkSparkle({
required MaterialInkController controller,
required RenderBox referenceBox,
required super.color,
required Offset position,
required TextDirection textDirection,
bool containedInkWell = true,
RectCallback? rectCallback,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
double? radius,
super.onRemoved,
double? turbulenceSeed,
}) : assert(containedInkWell || rectCallback == null),
_color = color,
_position = position,
_borderRadius = borderRadius ?? BorderRadius.zero,
_customBorder = customBorder,
_textDirection = textDirection,
_targetRadius = (radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position)) * _targetRadiusMultiplier,
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
super(controller: controller, referenceBox: referenceBox) {
// InkSparkle will not be painted until the async compilation completes.
_InkSparkleFactory.compileShaderIfNeccessary();
controller.addInkFeature(this);
// All animation values are derived from Android 12 source code. See:
// - https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
// - https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java
// - https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/drawable/RippleAnimationSession.java
// Immediately begin animating the ink.
_animationController = AnimationController(
duration: _animationDuration,
vsync: controller.vsync,
)..addListener(controller.markNeedsPaint)
..addStatusListener(_handleStatusChanged)
..forward();
_radiusScale = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: CurveTween(curve: Curves.fastOutSlowIn),
weight: 75,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 25,
),
],
).animate(_animationController);
// Functionally equivalent to Android 12's SkSL:
//`return mix(u_touch, u_resolution, saturate(in_radius_scale * 2.0))`
final Tween<Vector2> centerTween = Tween<Vector2>(
begin: Vector2.array(<double>[_position.dx, _position.dy]),
end: Vector2.array(<double>[referenceBox.size.width / 2, referenceBox.size.height / 2]),
);
final Animation<double> centerProgress = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 50,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 50,
),
],
).animate(_radiusScale);
_center = centerTween.animate(centerProgress);
_alpha = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 13,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 27,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 60,
),
],
).animate(_animationController);
_sparkleAlpha = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 13,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 27,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 50,
),
],
).animate(_animationController);
// Creates an element of randomness so that ink eminating from the same
// pixel have slightly different rings and sparkles.
_turbulenceSeed = turbulenceSeed ?? math.Random().nextDouble() * 1000.0;
}