blob: b48cd52ce09da684051889b8ebe1caba5d4ca8fa [file]
// 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.
import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'constants.dart';
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.
abstract class RenderToggleable extends RenderConstrainedBox implements SemanticActionHandler {
RenderToggleable({
bool value,
Size size,
Color activeColor,
Color inactiveColor,
ValueChanged<bool> onChanged,
double minRadialReactionRadius: 0.0
}) : _value = value,
_activeColor = activeColor,
_inactiveColor = inactiveColor,
_onChanged = onChanged,
super(additionalConstraints: new BoxConstraints.tight(size)) {
assert(value != null);
assert(activeColor != null);
assert(inactiveColor != null);
_tap = new TapGestureRecognizer()
..onTapDown = _handleTapDown
..onTap = _handleTap
..onTapUp = _handleTapUp
..onTapCancel = _handleTapCancel;
_positionController = new AnimationController(
duration: _kToggleDuration,
value: _value ? 1.0 : 0.0
);
_position = new CurvedAnimation(
parent: _positionController
)..addListener(markNeedsPaint)
..addStatusListener(_handlePositionStateChanged);
_reactionController = new AnimationController(duration: kRadialReactionDuration);
_reaction = new Tween<double>(
begin: minRadialReactionRadius,
end: kRadialReactionRadius
).animate(new CurvedAnimation(
parent: _reactionController,
curve: Curves.ease
))..addListener(markNeedsPaint);
}
bool get value => _value;
bool _value;
void set value(bool value) {
assert(value != null);
if (value == _value)
return;
_value = value;
markNeedsSemanticsUpdate(onlyChanges: true, noGeometry: true);
_position
..curve = Curves.easeIn
..reverseCurve = Curves.easeOut;
if (value)
_positionController.forward();
else
_positionController.reverse();
}
Color get activeColor => _activeColor;
Color _activeColor;
void set activeColor(Color value) {
assert(value != null);
if (value == _activeColor)
return;
_activeColor = value;
markNeedsPaint();
}
Color get inactiveColor => _inactiveColor;
Color _inactiveColor;
void set inactiveColor(Color value) {
assert(value != null);
if (value == _inactiveColor)
return;
_inactiveColor = value;
markNeedsPaint();
}
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);
}
}
bool get isInteractive => onChanged != null;
CurvedAnimation get position => _position;
CurvedAnimation _position;
AnimationController get positionController => _positionController;
AnimationController _positionController;
AnimationController get reactionController => _reactionController;
AnimationController _reactionController;
Animation<double> _reaction;
TapGestureRecognizer _tap;
void _handlePositionStateChanged(AnimationStatus status) {
if (isInteractive) {
if (status == AnimationStatus.completed && !_value)
onChanged(true);
else if (status == AnimationStatus.dismissed && _value)
onChanged(false);
}
}
void _handleTapDown(Point globalPosition) {
if (isInteractive)
_reactionController.forward();
}
void _handleTap() {
if (isInteractive)
onChanged(!_value);
}
void _handleTapUp(Point globalPosition) {
if (isInteractive)
_reactionController.reverse();
}
void _handleTapCancel() {
if (isInteractive)
_reactionController.reverse();
}
bool hitTestSelf(Point position) => true;
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
if (event is PointerDownEvent && isInteractive)
_tap.addPointer(event);
}
void paintRadialReaction(Canvas canvas, Offset offset) {
if (!_reaction.isDismissed) {
// TODO(abarth): We should have a different reaction color when position is zero.
Paint reactionPaint = new Paint()..color = activeColor.withAlpha(kRadialReactionAlpha);
canvas.drawCircle(offset.toPoint(), _reaction.value, reactionPaint);
}
}
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() { }
}