ink_highlight.dart 5.11 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

8
import 'ink_well.dart' show InteractiveInkFeature;
9 10
import 'material.dart';

11
const Duration _kDefaultHighlightFadeDuration = Duration(milliseconds: 200);
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

/// 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].
28 29
///  * [Ink], a convenience widget for drawing images and other decorations on
///    Material widgets.
30
class InkHighlight extends InteractiveInkFeature {
31 32 33 34 35 36 37 38 39 40
  /// 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({
41 42 43 44
    required MaterialInkController controller,
    required RenderBox referenceBox,
    required Color color,
    required TextDirection textDirection,
45
    BoxShape shape = BoxShape.rectangle,
46 47 48 49 50
    double? radius,
    BorderRadius? borderRadius,
    ShapeBorder? customBorder,
    RectCallback? rectCallback,
    VoidCallback? onRemoved,
51
    Duration fadeDuration = _kDefaultHighlightFadeDuration,
52 53
  }) : assert(color != null),
       assert(shape != null),
54
       assert(textDirection != null),
55
       assert(fadeDuration != null),
56
       _shape = shape,
57
       _radius = radius,
58
       _borderRadius = borderRadius ?? BorderRadius.zero,
59
       _customBorder = customBorder,
60
       _textDirection = textDirection,
61
       _rectCallback = rectCallback,
62
       super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
63
    _alphaController = AnimationController(duration: fadeDuration, vsync: controller.vsync)
64 65 66
      ..addListener(controller.markNeedsPaint)
      ..addStatusListener(_handleAlphaStatusChanged)
      ..forward();
67
    _alpha = _alphaController.drive(IntTween(
68
      begin: 0,
69 70
      end: color.alpha,
    ));
71 72 73 74 75

    controller.addInkFeature(this);
  }

  final BoxShape _shape;
76
  final double? _radius;
77
  final BorderRadius _borderRadius;
78 79
  final ShapeBorder? _customBorder;
  final RectCallback? _rectCallback;
80
  final TextDirection _textDirection;
81

82 83
  late Animation<int> _alpha;
  late AnimationController _alphaController;
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

  /// 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);
114 115
    canvas.save();
    if (_customBorder != null) {
116
      canvas.clipPath(_customBorder!.getOuterPath(rect, textDirection: _textDirection));
117
    }
118 119
    switch (_shape) {
      case BoxShape.circle:
120
        canvas.drawCircle(rect.center, _radius ?? Material.defaultSplashRadius, paint);
121 122
        break;
      case BoxShape.rectangle:
123
        if (_borderRadius != BorderRadius.zero) {
124
          final RRect clipRRect = RRect.fromRectAndCorners(
125 126 127 128 129 130 131 132
            rect,
            topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
            bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
          );
          canvas.drawRRect(clipRRect, paint);
        } else {
          canvas.drawRect(rect, paint);
        }
133 134
        break;
    }
135
    canvas.restore();
136 137 138 139
  }

  @override
  void paintFeature(Canvas canvas, Matrix4 transform) {
140
    final Paint paint = Paint()..color = color.withAlpha(_alpha.value);
141 142
    final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
    final Rect rect = _rectCallback != null ? _rectCallback!() : Offset.zero & referenceBox.size;
143 144 145 146 147 148 149 150 151 152
    if (originOffset == null) {
      canvas.save();
      canvas.transform(transform.storage);
      _paintHighlight(canvas, rect, paint);
      canvas.restore();
    } else {
      _paintHighlight(canvas, rect.shift(originOffset), paint);
    }
  }
}