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
customBorder
is provided, it is used to determine the path for clipping. - If
customBorder
is null, andborderRadius
is provided, then the canvas is clipped by an RRect created fromborderRadius
. - If
borderRadius
is 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;
}