slider.dart 5.78 KB
Newer Older
1 2 3 4 5 6 7 8 9
// 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/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

import 'colors.dart';
10
import 'constants.dart';
11
import 'debug.dart';
12 13 14
import 'theme.dart';

class Slider extends StatelessComponent {
Hixie's avatar
Hixie committed
15 16 17 18 19
  Slider({
    Key key,
    this.value,
    this.min: 0.0,
    this.max: 1.0,
20
    this.activeColor,
Hixie's avatar
Hixie committed
21 22 23 24 25 26 27
    this.onChanged
  }) : super(key: key) {
    assert(value != null);
    assert(min != null);
    assert(max != null);
    assert(value >= min && value <= max);
  }
28 29

  final double value;
Hixie's avatar
Hixie committed
30 31
  final double min;
  final double max;
32
  final Color activeColor;
33 34
  final ValueChanged<double> onChanged;

Hixie's avatar
Hixie committed
35 36 37 38 39
  void _handleChanged(double value) {
    assert(onChanged != null);
    onChanged(value * (max - min) + min);
  }

40
  Widget build(BuildContext context) {
41
    assert(debugCheckHasMaterial(context));
42
    return new _SliderRenderObjectWidget(
Hixie's avatar
Hixie committed
43
      value: (value - min) / (max - min),
44
      activeColor: activeColor ?? Theme.of(context).accentColor,
Hixie's avatar
Hixie committed
45
      onChanged: onChanged != null ? _handleChanged : null
46 47 48 49 50
    );
  }
}

class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
51
  _SliderRenderObjectWidget({ Key key, this.value, this.activeColor, this.onChanged })
52 53 54
      : super(key: key);

  final double value;
55
  final Color activeColor;
56 57 58 59
  final ValueChanged<double> onChanged;

  _RenderSlider createRenderObject() => new _RenderSlider(
    value: value,
60
    activeColor: activeColor,
61 62 63 64
    onChanged: onChanged
  );

  void updateRenderObject(_RenderSlider renderObject, _SliderRenderObjectWidget oldWidget) {
65 66 67 68
    renderObject
      ..value = value
      ..activeColor = activeColor
      ..onChanged = onChanged;
69 70 71
  }
}

72 73 74 75
const double _kThumbRadius = 6.0;
const double _kThumbRadiusDisabled = 3.0;
const double _kReactionRadius = 16.0;
const double _kTrackWidth = 144.0;
76 77 78 79 80 81
final Color _kInactiveTrackColor = Colors.grey[400];
final Color _kActiveTrackColor = Colors.grey[500];

class _RenderSlider extends RenderConstrainedBox {
  _RenderSlider({
    double value,
82
    Color activeColor,
83 84
    this.onChanged
  }) : _value = value,
85
       _activeColor = activeColor,
86 87
        super(additionalConstraints: const BoxConstraints.tightFor(width: _kTrackWidth + 2 * _kReactionRadius, height: 2 * _kReactionRadius)) {
    assert(value != null && value >= 0.0 && value <= 1.0);
88
    _drag = new HorizontalDragGestureRecognizer()
89 90 91
      ..onStart = _handleDragStart
      ..onUpdate = _handleDragUpdate
      ..onEnd = _handleDragEnd;
92 93 94 95 96 97 98 99
    _reactionController = new AnimationController(duration: kRadialReactionDuration);
    _reaction = new Tween<double>(
      begin: _kThumbRadius,
      end: _kReactionRadius
    ).animate(new CurvedAnimation(
      parent: _reactionController,
      curve: Curves.ease
    ))..addListener(markNeedsPaint);
100 101 102 103 104 105 106 107 108 109 110 111
  }

  double get value => _value;
  double _value;
  void set value(double newValue) {
    assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
    if (newValue == _value)
      return;
    _value = newValue;
    markNeedsPaint();
  }

112 113 114 115
  Color get activeColor => _activeColor;
  Color _activeColor;
  void set activeColor(Color value) {
    if (value == _activeColor)
116
      return;
117
    _activeColor = value;
118 119 120 121 122 123
    markNeedsPaint();
  }

  ValueChanged<double> onChanged;

  double get _trackLength => size.width - 2.0 * _kReactionRadius;
124

125
  Animation<double> _reaction;
126
  AnimationController _reactionController;
127 128 129 130 131 132 133 134

  HorizontalDragGestureRecognizer _drag;
  bool _active = false;
  double _currentDragValue = 0.0;

  void _handleDragStart(Point globalPosition) {
    if (onChanged != null) {
      _active = true;
Hixie's avatar
Hixie committed
135
      _currentDragValue = (globalToLocal(globalPosition).x - _kReactionRadius) / _trackLength;
136
      onChanged(_currentDragValue.clamp(0.0, 1.0));
137
      _reactionController.forward();
138 139 140 141 142 143 144 145 146 147 148
      markNeedsPaint();
    }
  }

  void _handleDragUpdate(double delta) {
    if (onChanged != null) {
      _currentDragValue += delta / _trackLength;
      onChanged(_currentDragValue.clamp(0.0, 1.0));
    }
  }

149
  void _handleDragEnd(Velocity velocity) {
150 151 152
    if (_active) {
      _active = false;
      _currentDragValue = 0.0;
153
      _reactionController.reverse();
154 155 156 157 158 159
      markNeedsPaint();
    }
  }

  bool hitTestSelf(Point position) => true;

Ian Hickson's avatar
Ian Hickson committed
160 161
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent && onChanged != null)
162 163 164 165 166 167 168
      _drag.addPointer(event);
  }

  void paint(PaintingContext context, Offset offset) {
    final Canvas canvas = context.canvas;

    final double trackLength = _trackLength;
169
    final bool enabled = onChanged != null;
170 171 172 173 174 175 176 177

    double trackCenter = offset.dy + size.height / 2.0;
    double trackLeft = offset.dx + _kReactionRadius;
    double trackTop = trackCenter - 1.0;
    double trackBottom = trackCenter + 1.0;
    double trackRight = trackLeft + trackLength;
    double trackActive = trackLeft + trackLength * value;

178
    Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _kInactiveTrackColor;
179 180
    Paint trackPaint = new Paint()..color = _active ? _kActiveTrackColor : _kInactiveTrackColor;

181 182
    double thumbRadius = enabled ? _kThumbRadius : _kThumbRadiusDisabled;

183
    canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), trackPaint);
184 185
    if (_value > 0.0)
      canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive, trackBottom), primaryPaint);
186 187

    Point activeLocation = new Point(trackActive, trackCenter);
188
    if (_reaction.status != AnimationStatus.dismissed) {
189
      Paint reactionPaint = new Paint()..color = _activeColor.withAlpha(kRadialReactionAlpha);
190
      canvas.drawCircle(activeLocation, _reaction.value, reactionPaint);
191
    }
192
    canvas.drawCircle(activeLocation, thumbRadius, primaryPaint);
193 194
  }
}