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;
69
    _positionController.play(value ? AnimationDirection.forward : AnimationDirection.reverse);
70 71
  }

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

  Color get inactiveColor => _inactiveColor;
  Color _inactiveColor;
  void set inactiveColor(Color value) {
    assert(value != null);
    if (value == _inactiveColor)
      return;
    _inactiveColor = value;
89 90 91
    markNeedsPaint();
  }

Hixie's avatar
Hixie committed
92 93 94 95 96 97 98 99 100 101 102 103
  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);
    }
  }
104

Hixie's avatar
Hixie committed
105
  bool get isInteractive => onChanged != null;
106

107 108 109 110 111
  CurvedAnimation get position => _position;
  CurvedAnimation _position;

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

113 114
  AnimationController get reactionController => _reactionController;
  AnimationController _reactionController;
115
  Animation<double> _reaction;
Hixie's avatar
Hixie committed
116

117 118
  TapGestureRecognizer _tap;

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

128 129
  void _handleTapDown(Point globalPosition) {
    if (isInteractive)
130
      _reactionController.forward();
131 132
  }

133
  void _handleTap() {
134
    if (isInteractive)
135
      onChanged(!_value);
136
  }
Adam Barth's avatar
Adam Barth committed
137

138 139
  void _handleTapUp(Point globalPosition) {
    if (isInteractive)
140
      _reactionController.reverse();
141 142 143 144
  }

  void _handleTapCancel() {
    if (isInteractive)
145
      _reactionController.reverse();
146 147
  }

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

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

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

  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() { }
178
}