switch.dart 6.9 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
import 'dart:ui' as ui;
6

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

12
import 'colors.dart';
13
import 'constants.dart';
14
import 'debug.dart';
Adam Barth's avatar
Adam Barth committed
15
import 'shadows.dart';
16
import 'theme.dart';
17
import 'toggleable.dart';
18

19
class Switch extends StatelessComponent {
20
  Switch({ Key key, this.value, this.activeColor, this.onChanged })
21 22 23
      : super(key: key);

  final bool value;
24
  final Color activeColor;
Hixie's avatar
Hixie committed
25
  final ValueChanged<bool> onChanged;
26

27
  Widget build(BuildContext context) {
28
    assert(debugCheckHasMaterial(context));
29 30 31
    ThemeData themeData = Theme.of(context);
    final isDark = themeData.brightness == ThemeBrightness.dark;

32
    Color activeThumbColor = activeColor ?? themeData.accentColor;
33 34 35 36 37 38 39 40 41 42 43 44
    Color activeTrackColor = activeThumbColor.withAlpha(0x80);

    Color inactiveThumbColor;
    Color inactiveTrackColor;
    if (onChanged != null) {
      inactiveThumbColor = isDark ? Colors.grey[400] : Colors.grey[50];
      inactiveTrackColor = isDark ? Colors.white30 : Colors.black26;
    } else {
      inactiveThumbColor = isDark ? Colors.grey[800] : Colors.grey[400];
      inactiveTrackColor = isDark ? Colors.white10 : Colors.black12;
    }

45
    return new _SwitchRenderObjectWidget(
46
      value: value,
47 48 49 50
      activeColor: activeThumbColor,
      inactiveColor: inactiveThumbColor,
      activeTrackColor: activeTrackColor,
      inactiveTrackColor: inactiveTrackColor,
51 52 53 54 55
      onChanged: onChanged
    );
  }
}

56 57 58 59
class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
  _SwitchRenderObjectWidget({
    Key key,
    this.value,
60 61 62 63
    this.activeColor,
    this.inactiveColor,
    this.activeTrackColor,
    this.inactiveTrackColor,
64 65
    this.onChanged
  }) : super(key: key);
66 67

  final bool value;
68 69 70 71
  final Color activeColor;
  final Color inactiveColor;
  final Color activeTrackColor;
  final Color inactiveTrackColor;
Hixie's avatar
Hixie committed
72
  final ValueChanged<bool> onChanged;
73 74

  _RenderSwitch createRenderObject() => new _RenderSwitch(
75
    value: value,
76 77 78 79
    activeColor: activeColor,
    inactiveColor: inactiveColor,
    activeTrackColor: activeTrackColor,
    inactiveTrackColor: inactiveTrackColor,
80 81
    onChanged: onChanged
  );
82

83
  void updateRenderObject(_RenderSwitch renderObject, _SwitchRenderObjectWidget oldWidget) {
84
    renderObject.value = value;
85 86 87 88
    renderObject.activeColor = activeColor;
    renderObject.inactiveColor = inactiveColor;
    renderObject.activeTrackColor = activeTrackColor;
    renderObject.inactiveTrackColor = inactiveTrackColor;
89
    renderObject.onChanged = onChanged;
90 91 92
  }
}

93 94 95 96 97 98 99
const double _kTrackHeight = 14.0;
const double _kTrackWidth = 29.0;
const double _kTrackRadius = _kTrackHeight / 2.0;
const double _kThumbRadius = 10.0;
const double _kSwitchWidth = _kTrackWidth - 2 * _kTrackRadius + 2 * kRadialReactionRadius;
const double _kSwitchHeight = 2 * kRadialReactionRadius;

100
class _RenderSwitch extends RenderToggleable {
101 102
  _RenderSwitch({
    bool value,
103 104 105 106
    Color activeColor,
    Color inactiveColor,
    Color activeTrackColor,
    Color inactiveTrackColor,
Hixie's avatar
Hixie committed
107
    ValueChanged<bool> onChanged
108
  }) : super(
109 110 111 112 113 114 115
     value: value,
     activeColor: activeColor,
     inactiveColor: inactiveColor,
     onChanged: onChanged,
     minRadialReactionRadius: _kThumbRadius,
     size: const Size(_kSwitchWidth, _kSwitchHeight)
   ) {
116 117
    _activeTrackColor = activeTrackColor;
    _inactiveTrackColor = inactiveTrackColor;
118
    _drag = new HorizontalDragGestureRecognizer(router: Gesturer.instance.pointerRouter, gestureArena: Gesturer.instance.gestureArena)
119 120 121
      ..onStart = _handleDragStart
      ..onUpdate = _handleDragUpdate
      ..onEnd = _handleDragEnd;
122 123
  }

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  Color get activeTrackColor => _activeTrackColor;
  Color _activeTrackColor;
  void set activeTrackColor(Color value) {
    assert(value != null);
    if (value == _activeTrackColor)
      return;
    _activeTrackColor = value;
    markNeedsPaint();
  }

  Color get inactiveTrackColor => _inactiveTrackColor;
  Color _inactiveTrackColor;
  void set inactiveTrackColor(Color value) {
    assert(value != null);
    if (value == _inactiveTrackColor)
      return;
    _inactiveTrackColor = value;
    markNeedsPaint();
  }

144
  double get _trackInnerLength => size.width - 2.0 * kRadialReactionRadius;
145

146 147 148 149 150 151 152 153 154 155 156 157 158
  HorizontalDragGestureRecognizer _drag;

  void _handleDragStart(Point globalPosition) {
    if (onChanged != null)
      reaction.forward();
  }

  void _handleDragUpdate(double delta) {
    if (onChanged != null) {
      position.variable
        ..curve = null
        ..reverseCurve = null;
      position.progress += delta / _trackInnerLength;
159 160 161
    }
  }

162 163 164 165 166 167
  void _handleDragEnd(Offset velocity) {
    if (position.progress >= 0.5)
      position.forward();
    else
      position.reverse();
    reaction.reverse();
168 169
  }

Ian Hickson's avatar
Ian Hickson committed
170 171
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent && onChanged != null)
172 173
      _drag.addPointer(event);
    super.handleEvent(event, entry);
174 175
  }

176 177
  Color _cachedThumbColor;
  BoxPainter _thumbPainter;
178

179
  void paint(PaintingContext context, Offset offset) {
Adam Barth's avatar
Adam Barth committed
180
    final Canvas canvas = context.canvas;
181

182 183 184 185
    final bool isActive = onChanged != null;

    Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, position.progress) : inactiveColor;
    Color trackColor = isActive ? Color.lerp(inactiveTrackColor, activeTrackColor, position.progress) : inactiveTrackColor;
186

187
    // Paint the track
188
    Paint paint = new Paint()
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
      ..color = trackColor;
    double trackHorizontalPadding = kRadialReactionRadius - _kTrackRadius;
    Rect trackRect = new Rect.fromLTWH(
      offset.dx + trackHorizontalPadding,
      offset.dy + (size.height - _kTrackHeight) / 2.0,
      size.width - 2.0 * trackHorizontalPadding,
      _kTrackHeight
    );
    ui.RRect trackRRect = new ui.RRect.fromRectXY(
        trackRect, _kTrackRadius, _kTrackRadius);
    canvas.drawRRect(trackRRect, paint);

    Offset thumbOffset = new Offset(
      offset.dx + kRadialReactionRadius + position.value * _trackInnerLength,
      offset.dy + size.height / 2.0);

    paintRadialReaction(canvas, thumbOffset);

207 208 209 210 211 212 213 214
    if (_cachedThumbColor != thumbColor) {
      _thumbPainter = new BoxDecoration(
        backgroundColor: thumbColor,
        shape: BoxShape.circle,
        boxShadow: elevationToShadow[1]
      ).createBoxPainter();
      _cachedThumbColor = thumbColor;
    }
215 216

    // The thumb contracts slightly during the animation
217 218 219 220 221 222 223
    double inset = 2.0 - (position.value - 0.5).abs() * 2.0;
    double radius = _kThumbRadius - inset;
    Rect thumbRect = new Rect.fromLTRB(thumbOffset.dx - radius,
                                       thumbOffset.dy - radius,
                                       thumbOffset.dx + radius,
                                       thumbOffset.dy + radius);
    _thumbPainter.paint(canvas, thumbRect);
224 225
  }
}