slider_demo.dart 8.94 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 'dart:math' as math;

7 8
import 'package:flutter/material.dart';

9 10
import '../../gallery/demo.dart';

11
class SliderDemo extends StatefulWidget {
12
  static const String routeName = '/material/slider';
13

14
  @override
15
  _SliderDemoState createState() => _SliderDemoState();
16 17
}

18
Path _triangle(double size, Offset thumbCenter, {bool invert = false}) {
19
  final Path thumbPath = Path();
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
  final double height = math.sqrt(3.0) / 2.0;
  final double halfSide = size / 2.0;
  final double centerHeight = size * height / 3.0;
  final double sign = invert ? -1.0 : 1.0;
  thumbPath.moveTo(thumbCenter.dx - halfSide, thumbCenter.dy + sign * centerHeight);
  thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2.0 * sign * centerHeight);
  thumbPath.lineTo(thumbCenter.dx + halfSide, thumbCenter.dy + sign * centerHeight);
  thumbPath.close();
  return thumbPath;
}

class _CustomThumbShape extends SliderComponentShape {
  static const double _thumbSize = 4.0;
  static const double _disabledThumbSize = 3.0;

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize);
  }

40
  static final Animatable<double> sizeTween = Tween<double>(
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
    begin: _disabledThumbSize,
    end: _thumbSize,
  );

  @override
  void paint(
    PaintingContext context,
    Offset thumbCenter, {
    Animation<double> activationAnimation,
    Animation<double> enableAnimation,
    bool isDiscrete,
    TextPainter labelPainter,
    RenderBox parentBox,
    SliderThemeData sliderTheme,
    TextDirection textDirection,
    double value,
  }) {
    final Canvas canvas = context.canvas;
59
    final ColorTween colorTween = ColorTween(
60 61 62 63 64
      begin: sliderTheme.disabledThumbColor,
      end: sliderTheme.thumbColor,
    );
    final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
    final Path thumbPath = _triangle(size, thumbCenter);
65
    canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
66 67 68 69 70 71 72 73 74 75
  }
}

class _CustomValueIndicatorShape extends SliderComponentShape {
  static const double _indicatorSize = 4.0;
  static const double _disabledIndicatorSize = 3.0;
  static const double _slideUpHeight = 40.0;

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
76
    return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
77 78
  }

79
  static final Animatable<double> sizeTween = Tween<double>(
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    begin: _disabledIndicatorSize,
    end: _indicatorSize,
  );

  @override
  void paint(
    PaintingContext context,
    Offset thumbCenter, {
    Animation<double> activationAnimation,
    Animation<double> enableAnimation,
    bool isDiscrete,
    TextPainter labelPainter,
    RenderBox parentBox,
    SliderThemeData sliderTheme,
    TextDirection textDirection,
    double value,
  }) {
    final Canvas canvas = context.canvas;
98
    final ColorTween enableColor = ColorTween(
99 100 101
      begin: sliderTheme.disabledThumbColor,
      end: sliderTheme.valueIndicatorColor,
    );
102
    final Tween<double> slideUpTween = Tween<double>(
103 104 105 106
      begin: 0.0,
      end: _slideUpHeight,
    );
    final double size = _indicatorSize * sizeTween.evaluate(enableAnimation);
107
    final Offset slideUpOffset = Offset(0.0, -slideUpTween.evaluate(activationAnimation));
108 109 110 111 112 113 114 115
    final Path thumbPath = _triangle(
      size,
      thumbCenter + slideUpOffset,
      invert: true,
    );
    final Color paintColor = enableColor.evaluate(enableAnimation).withAlpha((255.0 * activationAnimation.value).round());
    canvas.drawPath(
      thumbPath,
116
      Paint()..color = paintColor,
117 118 119 120
    );
    canvas.drawLine(
        thumbCenter,
        thumbCenter + slideUpOffset,
121
        Paint()
122 123 124
          ..color = paintColor
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2.0);
125
    labelPainter.paint(canvas, thumbCenter + slideUpOffset + Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0));
126 127 128
  }
}

129
class _SliderDemoState extends State<SliderDemo> {
Hixie's avatar
Hixie committed
130
  double _value = 25.0;
131
  double _discreteValue = 20.0;
132

133
  @override
134
  Widget build(BuildContext context) {
135
    final ThemeData theme = Theme.of(context);
136
    return Scaffold(
137 138 139 140
      appBar: AppBar(
        title: const Text('Sliders'),
        actions: <Widget>[MaterialDemoDocumentationButton(SliderDemo.routeName)],
      ),
141
      body: Padding(
142
        padding: const EdgeInsets.symmetric(horizontal: 40.0),
143
        child: Column(
144 145
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
146
            Column(
147
              mainAxisSize: MainAxisSize.min,
148
              children: <Widget>[
149
                Slider.adaptive(
150 151 152 153 154 155 156
                  value: _value,
                  min: 0.0,
                  max: 100.0,
                  onChanged: (double value) {
                    setState(() {
                      _value = value;
                    });
157
                  },
158
                ),
159
                const Text('Continuous'),
160
              ],
161
            ),
162 163 164 165 166 167
            Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Row(
                  children: <Widget>[
                    Expanded(
168
                      child: Slider.adaptive(
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
                        value: _value,
                        min: 0.0,
                        max: 100.0,
                        onChanged: (double value) {
                          setState(() {
                            _value = value;
                          });
                        },
                      ),
                    ),
                    Semantics(
                      label: 'Editable numerical value',
                      child: Container(
                        width: 48,
                        height: 48,
                        child: TextField(
                          onSubmitted: (String value) {
                            final double newValue = double.tryParse(value);
                            if (newValue != null && newValue != _value) {
                              setState(() {
                                _value = newValue.clamp(0, 100);
                              });
                            }
                          },
                          keyboardType: TextInputType.number,
                          controller: TextEditingController(
                            text: _value.toStringAsFixed(0),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
                const Text('Continuous with Editable Numerical Value'),
              ],
            ),
205
            Column(
206
              mainAxisSize: MainAxisSize.min,
207
              children: const <Widget>[
208
                Slider.adaptive(value: 0.25, onChanged: null),
209
                Text('Disabled'),
210
              ],
211
            ),
212
            Column(
213
              mainAxisSize: MainAxisSize.min,
214
              children: <Widget>[
215
                Slider.adaptive(
216 217
                  value: _discreteValue,
                  min: 0.0,
218
                  max: 200.0,
219 220 221 222 223 224
                  divisions: 5,
                  label: '${_discreteValue.round()}',
                  onChanged: (double value) {
                    setState(() {
                      _discreteValue = value;
                    });
225
                  },
226
                ),
227
                const Text('Discrete'),
228 229
              ],
            ),
230
            Column(
231 232
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
233
                SliderTheme(
234
                  data: theme.sliderTheme.copyWith(
235 236
                    activeTrackColor: Colors.deepPurple,
                    inactiveTrackColor: Colors.black26,
237 238 239 240 241
                    activeTickMarkColor: Colors.white70,
                    inactiveTickMarkColor: Colors.black,
                    overlayColor: Colors.black12,
                    thumbColor: Colors.deepPurple,
                    valueIndicatorColor: Colors.deepPurpleAccent,
242 243
                    thumbShape: _CustomThumbShape(),
                    valueIndicatorShape: _CustomValueIndicatorShape(),
244 245
                    valueIndicatorTextStyle: theme.accentTextTheme.body2.copyWith(color: Colors.black87),
                  ),
246
                  child: Slider(
247 248
                    value: _discreteValue,
                    min: 0.0,
249
                    max: 200.0,
250
                    divisions: 5,
251
                    semanticFormatterCallback: (double value) => value.round().toString(),
252 253 254 255 256 257 258 259 260 261 262
                    label: '${_discreteValue.round()}',
                    onChanged: (double value) {
                      setState(() {
                        _discreteValue = value;
                      });
                    },
                  ),
                ),
                const Text('Discrete with Custom Theme'),
              ],
            ),
263 264 265
          ],
        ),
      ),
266
    );
267 268
  }
}