1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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 RenderBox referenceBox,
required Color color,
required TextDirection textDirection,
BoxShape shape = BoxShape.rectangle,
double? radius,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
RectCallback? rectCallback,
VoidCallback? 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, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
_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);
}
}
}