blob: b947a4b6aa926b26d01fb26b20deafa65e2d34de [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'ink_well.dart' show InteractiveInkFeature;
import 'material.dart';
const Duration _kDefaultHighlightFadeDuration = Duration(milliseconds: 200);
/// A visual emphasis on a part of a [Material] receiving user interaction.
///
/// This object is rarely created directly. Instead of creating an ink highlight
/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
/// gestures (such as tap and long-press) to trigger ink highlights.
///
/// See also:
///
/// * [InkResponse], which uses gestures to trigger ink highlights and ink
/// splashes in the parent [Material].
/// * [InkWell], which is a rectangular [InkResponse] (the most common type of
/// ink response).
/// * [Material], which is the widget on which the ink highlight is painted.
/// * [InkSplash], which is an ink feature that shows a reaction to user input
/// on a [Material].
/// * [Ink], a convenience widget for drawing images and other decorations on
/// Material widgets.
class InkHighlight extends InteractiveInkFeature {
/// Begin a highlight animation.
///
/// The [controller] argument is typically obtained via
/// `Material.of(context)`.
///
/// If a `rectCallback` is given, then it provides the highlight rectangle,
/// otherwise, the highlight rectangle is coincident with the [referenceBox].
///
/// When the highlight is removed, `onRemoved` will be called.
InkHighlight({
required MaterialInkController controller,
required super.referenceBox,
required Color color,
required TextDirection textDirection,
BoxShape shape = BoxShape.rectangle,
double? radius,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
RectCallback? rectCallback,
super.onRemoved,
Duration fadeDuration = _kDefaultHighlightFadeDuration,
}) : assert(color != null),
assert(shape != null),
assert(textDirection != null),
assert(fadeDuration != null),
_shape = shape,
_radius = radius,
_borderRadius = borderRadius ?? BorderRadius.zero,
_customBorder = customBorder,
_textDirection = textDirection,
_rectCallback = rectCallback,
super(controller: controller, color: color) {
_alphaController = AnimationController(duration: fadeDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged)
..forward();
_alpha = _alphaController.drive(IntTween(
begin: 0,
end: color.alpha,
));
controller.addInkFeature(this);
}
final BoxShape _shape;
final double? _radius;
final BorderRadius _borderRadius;
final ShapeBorder? _customBorder;
final RectCallback? _rectCallback;
final TextDirection _textDirection;
late Animation<int> _alpha;
late AnimationController _alphaController;
/// Whether this part of the material is being visually emphasized.
bool get active => _active;
bool _active = true;
/// Start visually emphasizing this part of the material.
void activate() {
_active = true;
_alphaController.forward();
}
/// Stop visually emphasizing this part of the material.
void deactivate() {
_active = false;
_alphaController.reverse();
}
void _handleAlphaStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed && !_active)
dispose();
}
@override
void dispose() {
_alphaController.dispose();
super.dispose();
}
void _paintHighlight(Canvas canvas, Rect rect, Paint paint) {
assert(_shape != null);
canvas.save();
if (_customBorder != null) {
canvas.clipPath(_customBorder!.getOuterPath(rect, textDirection: _textDirection));
}
switch (_shape) {
case BoxShape.circle:
canvas.drawCircle(rect.center, _radius ?? Material.defaultSplashRadius, paint);
break;
case BoxShape.rectangle:
if (_borderRadius != BorderRadius.zero) {
final RRect clipRRect = RRect.fromRectAndCorners(
rect,
topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
);
canvas.drawRRect(clipRRect, paint);
} else {
canvas.drawRect(rect, paint);
}
break;
}
canvas.restore();
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
final Paint paint = Paint()..color = color.withAlpha(_alpha.value);
final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
final Rect rect = _rectCallback != null ? _rectCallback!() : Offset.zero & referenceBox.size;
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
_paintHighlight(canvas, rect, paint);
canvas.restore();
} else {
_paintHighlight(canvas, rect.shift(originOffset), paint);
}
}
}