toggleable.dart 5.25 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 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
7
import 'package:flutter/rendering.dart';
8

9 10
import 'constants.dart';

11 12 13 14 15 16
const Duration _kToggleDuration = const Duration(milliseconds: 200);

// RenderToggleable is a base class for material style toggleable controls with
// toggle animations. It handles storing the current value, dispatching
// ValueChanged on a tap gesture and driving a changed animation. Subclasses are
// responsible for painting.
Hixie's avatar
Hixie committed
17
abstract class RenderToggleable extends RenderConstrainedBox implements SemanticActionHandler {
18 19 20
  RenderToggleable({
    bool value,
    Size size,
21 22
    Color activeColor,
    Color inactiveColor,
Hixie's avatar
Hixie committed
23
    ValueChanged<bool> onChanged,
24
    double minRadialReactionRadius: 0.0
25
  }) : _value = value,
26 27
       _activeColor = activeColor,
       _inactiveColor = inactiveColor,
Hixie's avatar
Hixie committed
28
       _onChanged = onChanged,
29
       super(additionalConstraints: new BoxConstraints.tight(size)) {
30 31 32
    assert(value != null);
    assert(activeColor != null);
    assert(inactiveColor != null);
33
    _tap = new TapGestureRecognizer()
34 35 36 37
      ..onTapDown = _handleTapDown
      ..onTap = _handleTap
      ..onTapUp = _handleTapUp
      ..onTapCancel = _handleTapCancel;
38 39

    _positionController = new AnimationController(
Hixie's avatar
Hixie committed
40
      duration: _kToggleDuration,
41 42 43 44
      value: _value ? 1.0 : 0.0
    );
    _position = new CurvedAnimation(
      parent: _positionController
45 46
    )..addListener(markNeedsPaint)
     ..addStatusListener(_handlePositionStateChanged);
47 48 49 50 51 52 53 54 55

    _reactionController = new AnimationController(duration: kRadialReactionDuration);
    _reaction = new Tween<double>(
      begin: minRadialReactionRadius,
      end: kRadialReactionRadius
    ).animate(new CurvedAnimation(
      parent: _reactionController,
      curve: Curves.ease
    ))..addListener(markNeedsPaint);
56 57
  }

58 59 60
  bool get value => _value;
  bool _value;
  void set value(bool value) {
61
    assert(value != null);
62 63 64
    if (value == _value)
      return;
    _value = value;
Hixie's avatar
Hixie committed
65
    markNeedsSemanticsUpdate(onlyChanges: true, noGeometry: true);
66
    _position
67 68
      ..curve = Curves.easeIn
      ..reverseCurve = Curves.easeOut;
Adam Barth's avatar
Adam Barth committed
69 70 71 72
    if (value)
      _positionController.forward();
    else
      _positionController.reverse();
73 74
  }

75 76 77 78 79
  Color get activeColor => _activeColor;
  Color _activeColor;
  void set activeColor(Color value) {
    assert(value != null);
    if (value == _activeColor)
80
      return;
81 82 83 84 85 86 87 88 89 90 91
    _activeColor = value;
    markNeedsPaint();
  }

  Color get inactiveColor => _inactiveColor;
  Color _inactiveColor;
  void set inactiveColor(Color value) {
    assert(value != null);
    if (value == _inactiveColor)
      return;
    _inactiveColor = value;
92 93 94
    markNeedsPaint();
  }

Hixie's avatar
Hixie committed
95 96 97 98 99 100 101 102 103 104 105 106
  ValueChanged<bool> get onChanged => _onChanged;
  ValueChanged<bool> _onChanged;
  void set onChanged(ValueChanged<bool> value) {
    if (value == _onChanged)
      return;
    final bool wasInteractive = isInteractive;
    _onChanged = value;
    if (wasInteractive != isInteractive) {
      markNeedsPaint();
      markNeedsSemanticsUpdate(noGeometry: true);
    }
  }
107

Hixie's avatar
Hixie committed
108
  bool get isInteractive => onChanged != null;
109

110 111 112 113 114
  CurvedAnimation get position => _position;
  CurvedAnimation _position;

  AnimationController get positionController => _positionController;
  AnimationController _positionController;
Hixie's avatar
Hixie committed
115

116 117
  AnimationController get reactionController => _reactionController;
  AnimationController _reactionController;
118
  Animation<double> _reaction;
Hixie's avatar
Hixie committed
119

120 121
  TapGestureRecognizer _tap;

122
  void _handlePositionStateChanged(AnimationStatus status) {
123
    if (isInteractive) {
124
      if (status == AnimationStatus.completed && !_value)
125
        onChanged(true);
126
      else if (status == AnimationStatus.dismissed && _value)
127 128
        onChanged(false);
    }
129 130
  }

131 132
  void _handleTapDown(Point globalPosition) {
    if (isInteractive)
133
      _reactionController.forward();
134 135
  }

136
  void _handleTap() {
137
    if (isInteractive)
138
      onChanged(!_value);
139
  }
Adam Barth's avatar
Adam Barth committed
140

141 142
  void _handleTapUp(Point globalPosition) {
    if (isInteractive)
143
      _reactionController.reverse();
144 145 146 147
  }

  void _handleTapCancel() {
    if (isInteractive)
148
      _reactionController.reverse();
149 150
  }

Adam Barth's avatar
Adam Barth committed
151
  bool hitTestSelf(Point position) => true;
152

Ian Hickson's avatar
Ian Hickson committed
153 154
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent && isInteractive)
155 156 157 158
      _tap.addPointer(event);
  }

  void paintRadialReaction(Canvas canvas, Offset offset) {
159
    if (!_reaction.isDismissed) {
160 161
      // TODO(abarth): We should have a different reaction color when position is zero.
      Paint reactionPaint = new Paint()..color = activeColor.withAlpha(kRadialReactionAlpha);
162
      canvas.drawCircle(offset.toPoint(), _reaction.value, reactionPaint);
163 164
    }
  }
Hixie's avatar
Hixie committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

  bool get hasSemantics => isInteractive;
  Iterable<SemanticAnnotator> getSemanticAnnotators() sync* {
    yield (SemanticsNode semantics) {
      semantics.hasCheckedState = true;
      semantics.isChecked = _value;
      semantics.canBeTapped = isInteractive;
    };
  }
  void handleSemanticTap() => _handleTap();

  void handleSemanticLongPress() { }
  void handleSemanticScrollLeft() { }
  void handleSemanticScrollRight() { }
  void handleSemanticScrollUp() { }
  void handleSemanticScrollDown() { }
181
}