mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Adds an adaptive constructor for the Material Slider. An adaptive widget is one that renders itself as Material on Android, and Cupertino on iOS. This work is based off of a similar feature on Switches: bbb080b#diff-fe2bb980c6207699cbf45538fe927afa. The motivation for this change is that we should provide adaptive constructors for as many widgets as necessary in the Material library. In Material, it is suggested that the slider is an iOS-style slider.
269 lines
8.9 KiB
Dart
269 lines
8.9 KiB
Dart
// 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 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../gallery/demo.dart';
|
|
|
|
class SliderDemo extends StatefulWidget {
|
|
static const String routeName = '/material/slider';
|
|
|
|
@override
|
|
_SliderDemoState createState() => _SliderDemoState();
|
|
}
|
|
|
|
Path _triangle(double size, Offset thumbCenter, {bool invert = false}) {
|
|
final Path thumbPath = Path();
|
|
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);
|
|
}
|
|
|
|
static final Animatable<double> sizeTween = Tween<double>(
|
|
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;
|
|
final ColorTween colorTween = ColorTween(
|
|
begin: sliderTheme.disabledThumbColor,
|
|
end: sliderTheme.thumbColor,
|
|
);
|
|
final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
|
|
final Path thumbPath = _triangle(size, thumbCenter);
|
|
canvas.drawPath(thumbPath, Paint()..color = colorTween.evaluate(enableAnimation));
|
|
}
|
|
}
|
|
|
|
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) {
|
|
return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
|
|
}
|
|
|
|
static final Animatable<double> sizeTween = Tween<double>(
|
|
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;
|
|
final ColorTween enableColor = ColorTween(
|
|
begin: sliderTheme.disabledThumbColor,
|
|
end: sliderTheme.valueIndicatorColor,
|
|
);
|
|
final Tween<double> slideUpTween = Tween<double>(
|
|
begin: 0.0,
|
|
end: _slideUpHeight,
|
|
);
|
|
final double size = _indicatorSize * sizeTween.evaluate(enableAnimation);
|
|
final Offset slideUpOffset = Offset(0.0, -slideUpTween.evaluate(activationAnimation));
|
|
final Path thumbPath = _triangle(
|
|
size,
|
|
thumbCenter + slideUpOffset,
|
|
invert: true,
|
|
);
|
|
final Color paintColor = enableColor.evaluate(enableAnimation).withAlpha((255.0 * activationAnimation.value).round());
|
|
canvas.drawPath(
|
|
thumbPath,
|
|
Paint()..color = paintColor,
|
|
);
|
|
canvas.drawLine(
|
|
thumbCenter,
|
|
thumbCenter + slideUpOffset,
|
|
Paint()
|
|
..color = paintColor
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2.0);
|
|
labelPainter.paint(canvas, thumbCenter + slideUpOffset + Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0));
|
|
}
|
|
}
|
|
|
|
class _SliderDemoState extends State<SliderDemo> {
|
|
double _value = 25.0;
|
|
double _discreteValue = 20.0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ThemeData theme = Theme.of(context);
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Sliders'),
|
|
actions: <Widget>[MaterialDemoDocumentationButton(SliderDemo.routeName)],
|
|
),
|
|
body: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: <Widget>[
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Slider.adaptive(
|
|
value: _value,
|
|
min: 0.0,
|
|
max: 100.0,
|
|
onChanged: (double value) {
|
|
setState(() {
|
|
_value = value;
|
|
});
|
|
},
|
|
),
|
|
const Text('Continuous'),
|
|
],
|
|
),
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Row(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: Slider.adaptive(
|
|
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'),
|
|
],
|
|
),
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: const <Widget>[
|
|
Slider.adaptive(value: 0.25, onChanged: null),
|
|
Text('Disabled'),
|
|
],
|
|
),
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Slider.adaptive(
|
|
value: _discreteValue,
|
|
min: 0.0,
|
|
max: 200.0,
|
|
divisions: 5,
|
|
label: '${_discreteValue.round()}',
|
|
onChanged: (double value) {
|
|
setState(() {
|
|
_discreteValue = value;
|
|
});
|
|
},
|
|
),
|
|
const Text('Discrete'),
|
|
],
|
|
),
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
SliderTheme(
|
|
data: theme.sliderTheme.copyWith(
|
|
activeTrackColor: Colors.deepPurple,
|
|
inactiveTrackColor: Colors.black26,
|
|
activeTickMarkColor: Colors.white70,
|
|
inactiveTickMarkColor: Colors.black,
|
|
overlayColor: Colors.black12,
|
|
thumbColor: Colors.deepPurple,
|
|
valueIndicatorColor: Colors.deepPurpleAccent,
|
|
thumbShape: _CustomThumbShape(),
|
|
valueIndicatorShape: _CustomValueIndicatorShape(),
|
|
valueIndicatorTextStyle: theme.accentTextTheme.body2.copyWith(color: Colors.black87),
|
|
),
|
|
child: Slider(
|
|
value: _discreteValue,
|
|
min: 0.0,
|
|
max: 200.0,
|
|
divisions: 5,
|
|
semanticFormatterCallback: (double value) => value.round().toString(),
|
|
label: '${_discreteValue.round()}',
|
|
onChanged: (double value) {
|
|
setState(() {
|
|
_discreteValue = value;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
const Text('Discrete with Custom Theme'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|