ink_well.dart 24.2 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:collection';

xster's avatar
xster committed
7
import 'package:flutter/foundation.dart';
8 9 10
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
11

12
import 'debug.dart';
13
import 'feedback.dart';
14
import 'ink_highlight.dart';
15 16
import 'material.dart';
import 'theme.dart';
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
/// An ink feature that displays a [color] "splash" in response to a user
/// gesture that can be confirmed or canceled.
///
/// Subclasses call [confirm] when an input gesture is recognized. For
/// example a press event might trigger an ink feature that's confirmed
/// when the corresponding up event is seen.
///
/// Subclasses call [cancel] when an input gesture is aborted before it
/// is recognized. For example a press event might trigger an ink feature
/// that's cancelled when the pointer is dragged out of the reference
/// box.
///
/// The [InkWell] and [InkResponse] widgets generate instances of this
/// class.
abstract class InteractiveInkFeature extends InkFeature {
  /// Creates an InteractiveInkFeature.
  ///
  /// The [controller] and [referenceBox] arguments must not be null.
  InteractiveInkFeature({
    @required MaterialInkController controller,
    @required RenderBox referenceBox,
    Color color,
    VoidCallback onRemoved,
  }) : assert(controller != null),
       assert(referenceBox != null),
       _color = color,
       super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved);

  /// Called when the user input that triggered this feature's appearance was confirmed.
  ///
  /// Typically causes the ink to propagate faster across the material. By default this
  /// method does nothing.
  void confirm() {
  }

  /// Called when the user input that triggered this feature's appearance was canceled.
  ///
  /// Typically causes the ink to gradually disappear. By default this method does
  /// nothing.
  void cancel() {
  }

  /// The ink's color.
  Color get color => _color;
  Color _color;
  set color(Color value) {
    if (value == _color)
      return;
    _color = value;
    controller.markNeedsPaint();
  }
}

/// An encapsulation of an [InteractiveInkFeature] constructor used by [InkWell]
/// [InkResponse] and [ThemeData].
///
/// Interactive ink feature implementations should provide a static const
/// `splashFactory` value that's an instance of this class. The `splashFactory`
/// can be used to configure an [InkWell], [InkResponse] or [ThemeData].
///
/// See also:
///
///  * [InkSplash.splashFactory]
///  * [InkRipple.splashFactory]
abstract class InteractiveInkFeatureFactory {
  /// Subclasses should provide a const constructor.
  const InteractiveInkFeatureFactory();

  /// The factory method.
  ///
  /// Subclasses should override this method to return a new instance of an
  /// [InteractiveInkFeature].
  InteractiveInkFeature create({
    @required MaterialInkController controller,
    @required RenderBox referenceBox,
    @required Offset position,
    @required Color color,
95
    @required TextDirection textDirection,
96
    bool containedInkWell = false,
97 98
    RectCallback rectCallback,
    BorderRadius borderRadius,
99
    ShapeBorder customBorder,
100 101 102 103 104
    double radius,
    VoidCallback onRemoved,
  });
}

105
/// An area of a [Material] that responds to touch. Has a configurable shape and
106 107
/// can be configured to clip splashes that extend outside its bounds or not.
///
108
/// For a variant of this widget that is specialized for rectangular areas that
109 110
/// always clip splashes, see [InkWell].
///
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
/// An [InkResponse] widget does two things when responding to a tap:
///
///  * It starts to animate a _highlight_. The shape of the highlight is
///    determined by [highlightShape]. If it is a [BoxShape.circle], the
///    default, then the highlight is a circle of fixed size centered in the
///    [InkResponse]. If it is [BoxShape.rectangle], then the highlight is a box
///    the size of the [InkResponse] itself, unless [getRectCallback] is
///    provided, in which case that callback defines the rectangle. The color of
///    the highlight is set by [highlightColor].
///
///  * Simultaneously, it starts to animate a _splash_. This is a growing circle
///    initially centered on the tap location. If this is a [containedInkWell],
///    the splash grows to the [radius] while remaining centered at the tap
///    location. Otherwise, the splash migrates to the center of the box as it
///    grows.
///
127 128
/// The following two diagrams show how [InkResponse] looks when tapped if the
/// [highlightShape] is [BoxShape.circle] (the default) and [containedInkWell]
129 130 131 132
/// is false (also the default).
///
/// The first diagram shows how it looks if the [InkResponse] is relatively
/// large:
133
///
134
/// ![The highlight is a disc centered in the box, smaller than the child widget.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_large.png)
135 136 137
///
/// The second diagram shows how it looks if the [InkResponse] is small:
///
138
/// ![The highlight is a disc overflowing the box, centered on the child.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_small.png)
139
///
140 141 142
/// The main thing to notice from these diagrams is that the splashes happily
/// exceed the bounds of the widget (because [containedInkWell] is false).
///
143 144 145 146
/// The following diagram shows the effect when the [InkResponse] has a
/// [highlightShape] of [BoxShape.rectangle] with [containedInkWell] set to
/// true. These are the values used by [InkWell].
///
147
/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
148
///
149
/// The [InkResponse] widget must have a [Material] widget as an ancestor. The
150 151 152
/// [Material] widget is where the ink reactions are actually painted. This
/// matches the material design premise wherein the [Material] is what is
/// actually reacting to touches by spreading ink.
153 154
///
/// If a Widget uses this class directly, it should include the following line
155
/// at the top of its build function to call [debugCheckHasMaterial]:
156
///
157 158 159
/// ```dart
/// assert(debugCheckHasMaterial(context));
/// ```
Ian Hickson's avatar
Ian Hickson committed
160 161 162 163 164 165 166
///
/// ## Troubleshooting
///
/// ### The ink splashes aren't visible!
///
/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or
/// [DecoratedBox], between the [Material] widget and the [InkResponse] widget,
167 168 169 170 171 172 173 174 175 176 177 178 179
/// then the splash won't be visible because it will be under the opaque graphic.
/// This is because ink splashes draw on the underlying [Material] itself, as
/// if the ink was spreading inside the material.
///
/// The [Ink] widget can be used as a replacement for [Image], [Container], or
/// [DecoratedBox] to ensure that the image or decoration also paints in the
/// [Material] itself, below the ink.
///
/// If this is not possible for some reason, e.g. because you are using an
/// opaque [CustomPaint] widget, alternatively consider using a second
/// [Material] above the opaque widget but below the [InkResponse] (as an
/// ancestor to the ink response). The [MaterialType.transparency] material
/// kind can be used for this purpose.
180 181 182 183 184 185
///
/// See also:
///
///  * [GestureDetector], for listening for gestures without ink splashes.
///  * [RaisedButton] and [FlatButton], two kinds of buttons in material design.
///  * [IconButton], which combines [InkResponse] with an [Icon].
186
class InkResponse extends StatefulWidget {
187 188 189
  /// Creates an area of a [Material] that responds to touch.
  ///
  /// Must have an ancestor [Material] widget in which to cause ink reactions.
Ian Hickson's avatar
Ian Hickson committed
190 191 192
  ///
  /// The [containedInkWell], [highlightShape], [enableFeedback], and
  /// [excludeFromSemantics] arguments must not be null.
193
  const InkResponse({
194 195 196
    Key key,
    this.child,
    this.onTap,
197 198
    this.onTapDown,
    this.onTapCancel,
199
    this.onDoubleTap,
200
    this.onLongPress,
201
    this.onHighlightChanged,
202 203
    this.containedInkWell = false,
    this.highlightShape = BoxShape.circle,
204
    this.radius,
Ian Hickson's avatar
Ian Hickson committed
205
    this.borderRadius,
206
    this.customBorder,
207 208
    this.highlightColor,
    this.splashColor,
209
    this.splashFactory,
210 211
    this.enableFeedback = true,
    this.excludeFromSemantics = false,
Ian Hickson's avatar
Ian Hickson committed
212 213 214 215 216
  }) : assert(containedInkWell != null),
       assert(highlightShape != null),
       assert(enableFeedback != null),
       assert(excludeFromSemantics != null),
       super(key: key);
217

218
  /// The widget below this widget in the tree.
219 220
  ///
  /// {@macro flutter.widgets.child}
221
  final Widget child;
222

223
  /// Called when the user taps this part of the material.
224
  final GestureTapCallback onTap;
225

226 227 228 229 230 231 232
  /// Called when the user taps down this part of the material.
  final GestureTapDownCallback onTapDown;

  /// Called when the user cancels a tap that was started on this part of the
  /// material.
  final GestureTapCallback onTapCancel;

233
  /// Called when the user double taps this part of the material.
234
  final GestureTapCallback onDoubleTap;
235

236
  /// Called when the user long-presses on this part of the material.
237
  final GestureLongPressCallback onLongPress;
238

239 240
  /// Called when this part of the material either becomes highlighted or stops
  /// being highlighted.
241 242 243 244
  ///
  /// The value passed to the callback is true if this part of the material has
  /// become highlighted and false if this part of the material has stopped
  /// being highlighted.
245
  final ValueChanged<bool> onHighlightChanged;
246

247
  /// Whether this ink response should be clipped its bounds.
248 249 250 251 252 253 254 255 256 257 258 259
  ///
  /// This flag also controls whether the splash migrates to the center of the
  /// [InkResponse] or not. If [containedInkWell] is true, the splash remains
  /// centered around the tap location. If it is false, the splash migrates to
  /// the center of the [InkResponse] as it grows.
  ///
  /// See also:
  ///
  ///  * [highlightShape], which determines the shape of the highlight.
  ///  * [borderRadius], which controls the corners when the box is a rectangle.
  ///  * [getRectCallback], which controls the size and position of the box when
  ///    it is a rectangle.
260
  final bool containedInkWell;
261

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
  /// The shape (e.g., circle, rectangle) to use for the highlight drawn around
  /// this part of the material.
  ///
  /// If the shape is [BoxShape.circle], then the highlight is centered on the
  /// [InkResponse]. If the shape is [BoxShape.rectangle], then the highlight
  /// fills the [InkResponse], or the rectangle provided by [getRectCallback] if
  /// the callback is specified.
  ///
  /// See also:
  ///
  ///  * [containedInkWell], which controls clipping behavior.
  ///  * [borderRadius], which controls the corners when the box is a rectangle.
  ///  * [highlightColor], the color of the highlight.
  ///  * [getRectCallback], which controls the size and position of the box when
  ///    it is a rectangle.
277
  final BoxShape highlightShape;
278

279
  /// The radius of the ink splash.
280 281 282 283 284 285 286 287
  ///
  /// Splashes grow up to this size. By default, this size is determined from
  /// the size of the rectangle provided by [getRectCallback], or the size of
  /// the [InkResponse] itself.
  ///
  /// See also:
  ///
  ///  * [splashColor], the color of the splash.
288
  ///  * [splashFactory], which defines the appearance of the splash.
289 290
  final double radius;

291 292
  /// The clipping radius of the containing rect. This is effective only if
  /// [customBorder] is null.
Ian Hickson's avatar
Ian Hickson committed
293 294
  ///
  /// If this is null, it is interpreted as [BorderRadius.zero].
295 296
  final BorderRadius borderRadius;

297 298 299
  /// The custom clip border which overrides [borderRadius].
  final ShapeBorder customBorder;

300
  /// The highlight color of the ink response. If this property is null then the
301
  /// highlight color of the theme, [ThemeData.highlightColor], will be used.
302 303 304 305 306
  ///
  /// See also:
  ///
  ///  * [highlightShape], the shape of the highlight.
  ///  * [splashColor], the color of the splash.
307
  ///  * [splashFactory], which defines the appearance of the splash.
308 309 310
  final Color highlightColor;

  /// The splash color of the ink response. If this property is null then the
311
  /// splash color of the theme, [ThemeData.splashColor], will be used.
312 313 314
  ///
  /// See also:
  ///
315
  ///  * [splashFactory], which defines the appearance of the splash.
316 317
  ///  * [radius], the (maximum) size of the ink splash.
  ///  * [highlightColor], the color of the highlight.
318 319
  final Color splashColor;

320 321 322 323 324 325 326 327 328 329 330
  /// Defines the appearance of the splash.
  ///
  /// Defaults to the value of the theme's splash factory: [ThemeData.splashFactory].
  ///
  /// See also:
  ///
  ///  * [radius], the (maximum) size of the ink splash.
  ///  * [splashColor], the color of the splash.
  ///  * [highlightColor], the color of the highlight.
  ///  * [InkSplash.splashFactory], which defines the default splash.
  ///  * [InkRipple.splashFactory], which defines a splash that spreads out
331
  ///    more aggressively than the default.
332 333
  final InteractiveInkFeatureFactory splashFactory;

334 335 336 337 338 339 340 341 342 343
  /// Whether detected gestures should provide acoustic and/or haptic feedback.
  ///
  /// For example, on Android a tap will produce a clicking sound and a
  /// long-press will produce a short vibration, when feedback is enabled.
  ///
  /// See also:
  ///
  ///  * [Feedback] for providing platform-specific feedback to certain actions.
  final bool enableFeedback;

344 345 346 347 348 349 350 351 352
  /// Whether to exclude the gestures introduced by this widget from the
  /// semantics tree.
  ///
  /// For example, a long-press gesture for showing a tooltip is usually
  /// excluded because the tooltip itself is included in the semantics
  /// tree directly and so having a gesture to show it would result in
  /// duplication of information.
  final bool excludeFromSemantics;

353 354 355 356 357
  /// The rectangle to use for the highlight effect and for clipping
  /// the splash effects if [containedInkWell] is true.
  ///
  /// This method is intended to be overridden by descendants that
  /// specialize [InkResponse] for unusual cases. For example,
358
  /// [TableRowInkWell] implements this method to return the rectangle
359 360 361 362 363 364 365 366 367 368 369 370
  /// corresponding to the row that the widget is in.
  ///
  /// The default behavior returns null, which is equivalent to
  /// returning the referenceBox argument's bounding box (though
  /// slightly more efficient).
  RectCallback getRectCallback(RenderBox referenceBox) => null;

  /// Asserts that the given context satisfies the prerequisites for
  /// this class.
  ///
  /// This method is intended to be overridden by descendants that
  /// specialize [InkResponse] for unusual cases. For example,
371
  /// [TableRowInkWell] implements this method to verify that the widget is
372
  /// in a table.
373
  @mustCallSuper
374 375
  bool debugCheckContext(BuildContext context) {
    assert(debugCheckHasMaterial(context));
376
    assert(debugCheckHasDirectionality(context));
377 378 379
    return true;
  }

380
  @override
381
  _InkResponseState<InkResponse> createState() => _InkResponseState<InkResponse>();
Ian Hickson's avatar
Ian Hickson committed
382 383

  @override
384 385
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
Ian Hickson's avatar
Ian Hickson committed
386 387 388 389 390 391 392
    final List<String> gestures = <String>[];
    if (onTap != null)
      gestures.add('tap');
    if (onDoubleTap != null)
      gestures.add('double tap');
    if (onLongPress != null)
      gestures.add('long press');
393 394 395 396
    if (onTapDown != null)
      gestures.add('tap down');
    if (onTapCancel != null)
      gestures.add('tap cancel');
397 398 399
    properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
    properties.add(DiagnosticsProperty<bool>('containedInkWell', containedInkWell, level: DiagnosticLevel.fine));
    properties.add(DiagnosticsProperty<BoxShape>(
400 401 402 403 404
      'highlightShape',
      highlightShape,
      description: '${containedInkWell ? "clipped to " : ""}$highlightShape',
      showName: false,
    ));
Ian Hickson's avatar
Ian Hickson committed
405
  }
406 407
}

408
class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKeepAliveClientMixin {
409 410
  Set<InteractiveInkFeature> _splashes;
  InteractiveInkFeature _currentSplash;
411 412
  InkHighlight _lastHighlight;

413 414 415
  @override
  bool get wantKeepAlive => _lastHighlight != null || (_splashes != null && _splashes.isNotEmpty);

416 417 418 419 420
  void updateHighlight(bool value) {
    if (value == (_lastHighlight != null && _lastHighlight.active))
      return;
    if (value) {
      if (_lastHighlight == null) {
421
        final RenderBox referenceBox = context.findRenderObject();
422
        _lastHighlight = InkHighlight(
423
          controller: Material.of(context),
424
          referenceBox: referenceBox,
425 426 427
          color: widget.highlightColor ?? Theme.of(context).highlightColor,
          shape: widget.highlightShape,
          borderRadius: widget.borderRadius,
428
          customBorder: widget.customBorder,
429
          rectCallback: widget.getRectCallback(referenceBox),
430
          onRemoved: _handleInkHighlightRemoval,
431
          textDirection: Directionality.of(context),
432
        );
433
        updateKeepAlive();
434 435 436 437 438 439
      } else {
        _lastHighlight.activate();
      }
    } else {
      _lastHighlight.deactivate();
    }
440
    assert(value == (_lastHighlight != null && _lastHighlight.active));
441 442
    if (widget.onHighlightChanged != null)
      widget.onHighlightChanged(value);
443 444
  }

445 446 447 448 449 450
  void _handleInkHighlightRemoval() {
    assert(_lastHighlight != null);
    _lastHighlight = null;
    updateKeepAlive();
  }

451 452
  InteractiveInkFeature _createInkFeature(TapDownDetails details) {
    final MaterialInkController inkController = Material.of(context);
453
    final RenderBox referenceBox = context.findRenderObject();
454 455 456
    final Offset position = referenceBox.globalToLocal(details.globalPosition);
    final Color color = widget.splashColor ?? Theme.of(context).splashColor;
    final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null;
Ian Hickson's avatar
Ian Hickson committed
457
    final BorderRadius borderRadius = widget.borderRadius;
458
    final ShapeBorder customBorder = widget.customBorder;
459 460 461 462 463 464 465 466 467 468 469 470 471 472

    InteractiveInkFeature splash;
    void onRemoved() {
      if (_splashes != null) {
        assert(_splashes.contains(splash));
        _splashes.remove(splash);
        if (_currentSplash == splash)
          _currentSplash = null;
        updateKeepAlive();
      } // else we're probably in deactivate()
    }

    splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create(
      controller: inkController,
473
      referenceBox: referenceBox,
474 475
      position: position,
      color: color,
476
      containedInkWell: widget.containedInkWell,
477
      rectCallback: rectCallback,
478
      radius: widget.radius,
479
      borderRadius: borderRadius,
480
      customBorder: customBorder,
481
      onRemoved: onRemoved,
482
      textDirection: Directionality.of(context),
483
    );
484 485 486 487 488 489

    return splash;
  }

  void _handleTapDown(TapDownDetails details) {
    final InteractiveInkFeature splash = _createInkFeature(details);
490
    _splashes ??= HashSet<InteractiveInkFeature>();
491 492
    _splashes.add(splash);
    _currentSplash = splash;
493 494 495
    if (widget.onTapDown != null) {
      widget.onTapDown(details);
    }
496
    updateKeepAlive();
497
    updateHighlight(true);
498 499
  }

500
  void _handleTap(BuildContext context) {
501 502
    _currentSplash?.confirm();
    _currentSplash = null;
503
    updateHighlight(false);
504 505 506
    if (widget.onTap != null) {
      if (widget.enableFeedback)
        Feedback.forTap(context);
507
      widget.onTap();
508
    }
509 510
  }

511 512 513
  void _handleTapCancel() {
    _currentSplash?.cancel();
    _currentSplash = null;
514 515 516
    if (widget.onTapCancel != null) {
      widget.onTapCancel();
    }
517
    updateHighlight(false);
518 519
  }

520 521 522
  void _handleDoubleTap() {
    _currentSplash?.confirm();
    _currentSplash = null;
523 524
    if (widget.onDoubleTap != null)
      widget.onDoubleTap();
525 526
  }

527
  void _handleLongPress(BuildContext context) {
528 529
    _currentSplash?.confirm();
    _currentSplash = null;
530 531 532
    if (widget.onLongPress != null) {
      if (widget.enableFeedback)
        Feedback.forLongPress(context);
533
      widget.onLongPress();
534
    }
535 536
  }

537
  @override
538 539
  void deactivate() {
    if (_splashes != null) {
540
      final Set<InteractiveInkFeature> splashes = _splashes;
541
      _splashes = null;
542
      for (InteractiveInkFeature splash in splashes)
543 544 545 546
        splash.dispose();
      _currentSplash = null;
    }
    assert(_currentSplash == null);
547 548
    _lastHighlight?.dispose();
    _lastHighlight = null;
549
    super.deactivate();
550 551
  }

552
  @override
553
  Widget build(BuildContext context) {
554
    assert(widget.debugCheckContext(context));
555
    super.build(context); // See AutomaticKeepAliveClientMixin.
556
    final ThemeData themeData = Theme.of(context);
557 558 559
    _lastHighlight?.color = widget.highlightColor ?? themeData.highlightColor;
    _currentSplash?.color = widget.splashColor ?? themeData.splashColor;
    final bool enabled = widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
560
    return GestureDetector(
561
      onTapDown: enabled ? _handleTapDown : null,
562
      onTap: enabled ? () => _handleTap(context) : null,
563
      onTapCancel: enabled ? _handleTapCancel : null,
564
      onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
565
      onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null,
566
      behavior: HitTestBehavior.opaque,
567 568
      child: widget.child,
      excludeFromSemantics: widget.excludeFromSemantics,
569
    );
570 571
  }

572
}
573

574
/// A rectangular area of a [Material] that responds to touch.
575
///
576 577 578 579 580
/// For a variant of this widget that does not clip splashes, see [InkResponse].
///
/// The following diagram shows how an [InkWell] looks when tapped, when using
/// default values.
///
581
/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
582
///
583
/// The [InkWell] widget must have a [Material] widget as an ancestor. The
584 585 586 587
/// [Material] widget is where the ink reactions are actually painted. This
/// matches the material design premise wherein the [Material] is what is
/// actually reacting to touches by spreading ink.
///
588
/// If a Widget uses this class directly, it should include the following line
589
/// at the top of its build function to call [debugCheckHasMaterial]:
590
///
591 592 593 594
/// ```dart
/// assert(debugCheckHasMaterial(context));
/// ```
///
Ian Hickson's avatar
Ian Hickson committed
595 596 597 598 599 600
/// ## Troubleshooting
///
/// ### The ink splashes aren't visible!
///
/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or
/// [DecoratedBox], between the [Material] widget and the [InkWell] widget, then
601 602 603 604 605 606 607 608 609 610 611 612 613
/// the splash won't be visible because it will be under the opaque graphic.
/// This is because ink splashes draw on the underlying [Material] itself, as
/// if the ink was spreading inside the material.
///
/// The [Ink] widget can be used as a replacement for [Image], [Container], or
/// [DecoratedBox] to ensure that the image or decoration also paints in the
/// [Material] itself, below the ink.
///
/// If this is not possible for some reason, e.g. because you are using an
/// opaque [CustomPaint] widget, alternatively consider using a second
/// [Material] above the opaque widget but below the [InkWell] (as an
/// ancestor to the ink well). The [MaterialType.transparency] material
/// kind can be used for this purpose.
Ian Hickson's avatar
Ian Hickson committed
614
///
615 616 617 618 619 620
/// See also:
///
///  * [GestureDetector], for listening for gestures without ink splashes.
///  * [RaisedButton] and [FlatButton], two kinds of buttons in material design.
///  * [InkResponse], a variant of [InkWell] that doesn't force a rectangular
///    shape on the ink reaction.
621
class InkWell extends InkResponse {
622 623 624
  /// Creates an ink well.
  ///
  /// Must have an ancestor [Material] widget in which to cause ink reactions.
Ian Hickson's avatar
Ian Hickson committed
625 626 627
  ///
  /// The [enableFeedback] and [excludeFromSemantics] arguments must not be
  /// null.
628
  const InkWell({
629 630
    Key key,
    Widget child,
631
    GestureTapCallback onTap,
632
    GestureTapCallback onDoubleTap,
633
    GestureLongPressCallback onLongPress,
634 635
    GestureTapDownCallback onTapDown,
    GestureTapCancelCallback onTapCancel,
636 637 638
    ValueChanged<bool> onHighlightChanged,
    Color highlightColor,
    Color splashColor,
639 640
    InteractiveInkFeatureFactory splashFactory,
    double radius,
641
    BorderRadius borderRadius,
642
    ShapeBorder customBorder,
643 644
    bool enableFeedback = true,
    bool excludeFromSemantics = false,
645 646 647 648 649
  }) : super(
    key: key,
    child: child,
    onTap: onTap,
    onDoubleTap: onDoubleTap,
650
    onLongPress: onLongPress,
651 652
    onTapDown: onTapDown,
    onTapCancel: onTapCancel,
653
    onHighlightChanged: onHighlightChanged,
654
    containedInkWell: true,
655 656 657
    highlightShape: BoxShape.rectangle,
    highlightColor: highlightColor,
    splashColor: splashColor,
658 659
    splashFactory: splashFactory,
    radius: radius,
660
    borderRadius: borderRadius,
661
    customBorder: customBorder,
662
    enableFeedback: enableFeedback,
663
    excludeFromSemantics: excludeFromSemantics,
664
  );
665
}