ink_well.dart 5.79 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';

7 8 9
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
10

11
import 'debug.dart';
12 13
import 'material.dart';
import 'theme.dart';
14

15 16 17 18 19 20 21 22 23 24 25 26
/// An area of a Material that responds to touch. Has a configurable shape and
/// can be configured to clip splashes that extend outside its bounds or not.
///
/// For a variant of this widget that is specialised for rectangular areas that
/// always clip splashes, see [InkWell].
///
/// Must have an ancestor [Material] widget in which to cause ink reactions.
///
/// If a Widget uses this class directly, it should include the following line
/// at the top of its [build] function to call [debugCheckHasMaterial]:
///
///     assert(debugCheckHasMaterial(context));
27 28
class InkResponse extends StatefulComponent {
  InkResponse({
29 30 31
    Key key,
    this.child,
    this.onTap,
32
    this.onDoubleTap,
33
    this.onLongPress,
34 35
    this.onHighlightChanged,
    this.containedInWell: false,
36
    this.highlightShape: BoxShape.circle
37 38 39 40
  }) : super(key: key);

  final Widget child;
  final GestureTapCallback onTap;
41
  final GestureTapCallback onDoubleTap;
42
  final GestureLongPressCallback onLongPress;
43
  final ValueChanged<bool> onHighlightChanged;
44
  final bool containedInWell;
45
  final BoxShape highlightShape;
46

47
  _InkResponseState<InkResponse> createState() => new _InkResponseState<InkResponse>();
48 49
}

50
class _InkResponseState<T extends InkResponse> extends State<T> {
51

52 53
  Set<InkSplash> _splashes;
  InkSplash _currentSplash;
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  InkHighlight _lastHighlight;

  void updateHighlight(bool value) {
    if (value == (_lastHighlight != null && _lastHighlight.active))
      return;
    if (value) {
      if (_lastHighlight == null) {
        RenderBox referenceBox = context.findRenderObject();
        assert(Material.of(context) != null);
        _lastHighlight = Material.of(context).highlightAt(
          referenceBox: referenceBox,
          color: Theme.of(context).highlightColor,
          shape: config.highlightShape,
          onRemoved: () {
            assert(_lastHighlight != null);
            _lastHighlight = null;
          }
        );
      } else {
        _lastHighlight.activate();
      }
    } else {
      _lastHighlight.deactivate();
    }
78
    assert(value == (_lastHighlight != null && _lastHighlight.active));
79
    if (config.onHighlightChanged != null)
80
      config.onHighlightChanged(value);
81 82
  }

83

84 85 86 87 88 89 90
  void _handleTapDown(Point position) {
    RenderBox referenceBox = context.findRenderObject();
    assert(Material.of(context) != null);
    InkSplash splash;
    splash = Material.of(context).splashAt(
      referenceBox: referenceBox,
      position: referenceBox.globalToLocal(position),
91 92
      color: Theme.of(context).splashColor,
      containedInWell: config.containedInWell,
93 94 95 96 97 98 99 100 101
      onRemoved: () {
        if (_splashes != null) {
          assert(_splashes.contains(splash));
          _splashes.remove(splash);
          if (_currentSplash == splash)
            _currentSplash = null;
        } // else we're probably in deactivate()
      }
    );
102
    _splashes ??= new HashSet<InkSplash>();
103 104
    _splashes.add(splash);
    _currentSplash = splash;
105
    updateHighlight(true);
106 107
  }

108 109 110
  void _handleTap() {
    _currentSplash?.confirm();
    _currentSplash = null;
111
    updateHighlight(false);
112 113
    if (config.onTap != null)
      config.onTap();
114 115
  }

116 117 118
  void _handleTapCancel() {
    _currentSplash?.cancel();
    _currentSplash = null;
119
    updateHighlight(false);
120 121
  }

122 123 124 125 126
  void _handleDoubleTap() {
    _currentSplash?.confirm();
    _currentSplash = null;
    if (config.onDoubleTap != null)
      config.onDoubleTap();
127 128
  }

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
  void _handleLongPress() {
    _currentSplash?.confirm();
    _currentSplash = null;
    if (config.onLongPress != null)
      config.onLongPress();
  }

  void deactivate() {
    if (_splashes != null) {
      Set<InkSplash> splashes = _splashes;
      _splashes = null;
      for (InkSplash splash in splashes)
        splash.dispose();
      _currentSplash = null;
    }
    assert(_currentSplash == null);
145 146
    _lastHighlight?.dispose();
    _lastHighlight = null;
147
    super.deactivate();
148 149
  }

150 151 152 153 154
  void dependenciesChanged(Type affectedWidgetType) {
    if (affectedWidgetType == Theme && _lastHighlight != null)
      _lastHighlight.color = Theme.of(context).highlightColor;
  }

155
  Widget build(BuildContext context) {
156
    assert(debugCheckHasMaterial(context));
157 158 159 160 161 162 163 164 165 166
    final bool enabled = config.onTap != null || config.onDoubleTap != null || config.onLongPress != null;
    return new GestureDetector(
      onTapDown: enabled ? _handleTapDown : null,
      onTap: enabled ? _handleTap : null,
      onTapCancel: enabled ? _handleTapCancel : null,
      onDoubleTap: config.onDoubleTap != null ? _handleDoubleTap : null,
      onLongPress: config.onLongPress != null ? _handleLongPress : null,
      behavior: HitTestBehavior.opaque,
      child: config.child
    );
167 168
  }

169
}
170

171 172 173 174 175 176
/// A rectangular area of a Material that responds to touch.
///
/// Must have an ancestor [Material] widget in which to cause ink reactions.
///
/// If a Widget uses this class directly, it should include the following line
/// at the top of its [build] function to call [debugCheckHasMaterial]:
177
///
178
///     assert(debugCheckHasMaterial(context));
179 180 181 182
class InkWell extends InkResponse {
  InkWell({
    Key key,
    Widget child,
183
    GestureTapCallback onTap,
184
    GestureTapCallback onDoubleTap,
185
    GestureLongPressCallback onLongPress,
186
    ValueChanged<bool> onHighlightChanged
187 188 189 190 191
  }) : super(
    key: key,
    child: child,
    onTap: onTap,
    onDoubleTap: onDoubleTap,
192 193 194
    onLongPress: onLongPress,
    onHighlightChanged: onHighlightChanged,
    containedInWell: true,
195
    highlightShape: BoxShape.rectangle
196
  );
197
}