Commit 3176921d authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add CupertinoSlider (#7336)

This widget matches the style of the iOS slider widget. We don't yet
have the proper visual design for the disabled state.
parent 96fa6c3e
......@@ -7,4 +7,6 @@
/// To use, import `package:flutter/cupertino.dart`.
library cupertino;
export 'src/cupertino/slider.dart';
export 'src/cupertino/switch.dart';
export 'src/cupertino/thumb_painter.dart';
This diff is collapsed.
......@@ -10,6 +10,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'thumb_painter.dart';
/// An iOS-style switch.
///
/// Used to toggle the on/off state of a single setting.
......@@ -118,24 +120,18 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
}
}
const double _kTrackWidth = 51.0;
const double _kTrackHeight = 31.0;
const double _kTrackRadius = _kTrackHeight / 2.0;
const double _kTrackInnerStart = _kTrackHeight / 2.0;
const double _kTrackInnerEnd = _kTrackWidth - _kTrackInnerStart;
const double _kTrackInnerLength = _kTrackInnerEnd - _kTrackInnerStart;
const double _kThumbRadius = 14.0;
const double _kThumbExtension = 7.0;
const double _kSwitchWidth = 59.0;
const double _kSwitchHeight = 39.0;
const Color _kTrackColor = const Color(0xFFE5E5E5);
const Color _kThumbColor = const Color(0xFFFFFFFF);
const Color _kThumbShadowColor = const Color(0x2C000000);
const Duration _kReactionDuration = const Duration(milliseconds: 300);
const Duration _kToggleDuration = const Duration(milliseconds: 200);
final MaskFilter _kShadowMaskFilter = new MaskFilter.blur(BlurStyle.normal, BoxShadow.convertRadiusToSigma(1.0));
class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsActionHandler {
_RenderCupertinoSwitch({
......@@ -355,6 +351,8 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
_handleTap();
}
final CupertinoThumbPainter _thumbPainter = new CupertinoThumbPainter();
@override
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
......@@ -378,33 +376,25 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
final RRect innerRRect = new RRect.fromRectAndRadius(trackRect.deflate(borderThickness), const Radius.circular(_kTrackRadius));
canvas.drawDRRect(outerRRect, innerRRect, paint);
final double currentThumbExtension = _kThumbExtension * currentReactionValue;
final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue;
final double thumbLeft = lerpDouble(
trackRect.left + _kTrackInnerStart - _kThumbRadius,
trackRect.left + _kTrackInnerEnd - _kThumbRadius - currentThumbExtension,
trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
trackRect.left + _kTrackInnerEnd - CupertinoThumbPainter.radius - currentThumbExtension,
currentPosition,
);
final double thumbRight = lerpDouble(
trackRect.left + _kTrackInnerStart + _kThumbRadius + currentThumbExtension,
trackRect.left + _kTrackInnerEnd + _kThumbRadius,
trackRect.left + _kTrackInnerStart + CupertinoThumbPainter.radius + currentThumbExtension,
trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius,
currentPosition,
);
final double thumbCenterY = offset.dy + size.height / 2.0;
final RRect thumbRRect = new RRect.fromRectAndRadius(new Rect.fromLTRB(
thumbLeft, thumbCenterY - _kThumbRadius, thumbRight, thumbCenterY + _kThumbRadius
), const Radius.circular(_kThumbRadius));
paint
..color = _kThumbShadowColor
..maskFilter = _kShadowMaskFilter;
canvas.drawRRect(thumbRRect, paint);
canvas.drawRRect(thumbRRect.shift(const Offset(0.0, 3.0)), paint);
paint
..color = _kThumbColor
..maskFilter = null;
canvas.drawRRect(thumbRRect, paint);
_thumbPainter.paint(canvas, new Rect.fromLTRB(
thumbLeft,
thumbCenterY - CupertinoThumbPainter.radius,
thumbRight,
thumbCenterY + CupertinoThumbPainter.radius,
));
}
@override
......
// Copyright 2017 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/painting.dart';
final MaskFilter _kShadowMaskFilter = new MaskFilter.blur(BlurStyle.normal, BoxShadow.convertRadiusToSigma(1.0));
class CupertinoThumbPainter {
CupertinoThumbPainter({
this.color: const Color(0xFFFFFFFF),
this.shadowColor: const Color(0x2C000000),
});
final Color color;
final Color shadowColor;
static const double radius = 14.0;
static const double extension = 7.0;
void paint(Canvas canvas, Rect rect) {
final RRect rrect = new RRect.fromRectAndRadius(rect, new Radius.circular(rect.shortestSide / 2.0));
Paint paint = new Paint()
..color = shadowColor
..maskFilter = _kShadowMaskFilter;
canvas.drawRRect(rrect, paint);
canvas.drawRRect(rrect.shift(const Offset(0.0, 3.0)), paint);
paint
..color = color
..maskFilter = null;
canvas.drawRRect(rrect, paint);
}
}
......@@ -355,7 +355,6 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandl
_currentDragValue = (globalToLocal(details.globalPosition).x - _kReactionRadius) / _trackLength;
onChanged(_discretizedCurrentDragValue);
_reactionController.forward();
markNeedsPaint();
}
}
......@@ -371,7 +370,6 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandl
_active = false;
_currentDragValue = 0.0;
_reactionController.reverse();
markNeedsPaint();
}
}
......@@ -490,14 +488,15 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandl
@override
void performAction(SemanticsAction action) {
final double unit = divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
switch (action) {
case SemanticsAction.increase:
if (isInteractive)
onChanged((value + _kAdjustmentUnit).clamp(0.0, 1.0));
onChanged((value + unit).clamp(0.0, 1.0));
break;
case SemanticsAction.decrease:
if (isInteractive)
onChanged((value - _kAdjustmentUnit).clamp(0.0, 1.0));
onChanged((value - unit).clamp(0.0, 1.0));
break;
default:
assert(false);
......
// Copyright 2016 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/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Slider does not move when tapped', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey();
double value = 0.0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new CupertinoSlider(
key: sliderKey,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
);
expect(value, equals(0.0));
await tester.tap(find.byKey(sliderKey));
expect(value, equals(0.0));
await tester.pump(); // No animation should start.
// Check the transientCallbackCount before tearing down the widget to ensure
// that no animation is running.
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
});
testWidgets('Slider moves when dragged', (WidgetTester tester) async {
final Key sliderKey = new UniqueKey();
double value = 0.0;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new CupertinoSlider(
key: sliderKey,
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
);
expect(value, equals(0.0));
final Point topLeft = tester.getTopLeft(find.byKey(sliderKey));
const double unit = CupertinoThumbPainter.radius;
const double delta = 3.0 * unit;
await tester.scrollAt(
topLeft + const Offset(unit, unit), const Offset(delta, 0.0));
final Size size = tester.getSize(find.byKey(sliderKey));
expect(value, equals(delta / (size.width - 2.0 * (8.0 + CupertinoThumbPainter.radius))));
await tester.pump(); // No animation should start.
// Check the transientCallbackCount before tearing down the widget to ensure
// that no animation is running.
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment