mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Really basic Switch implementation in Sky. Could definitely use some polish
R=abarth@chromium.org, abarth Review URL: https://codereview.chromium.org/1185173002.
This commit is contained in:
parent
f8efd9c96f
commit
8758e72da5
@ -120,6 +120,8 @@ dart_pkg("sdk") {
|
||||
"lib/widgets/raised_button.dart",
|
||||
"lib/widgets/scaffold.dart",
|
||||
"lib/widgets/scrollable.dart",
|
||||
"lib/widgets/switch.dart",
|
||||
"lib/widgets/toggleable.dart",
|
||||
"lib/widgets/tool_bar.dart",
|
||||
"lib/widgets/ui_node.dart",
|
||||
"pubspec.yaml",
|
||||
|
||||
@ -6,130 +6,79 @@ import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/theme2/colors.dart' as colors;
|
||||
|
||||
import '../framework/animation/animated_value.dart';
|
||||
import '../framework/animation/curves.dart';
|
||||
import '../rendering/box.dart';
|
||||
import 'animated_component.dart';
|
||||
import 'basic.dart';
|
||||
|
||||
typedef void ValueChanged(value);
|
||||
import 'toggleable.dart';
|
||||
|
||||
const double _kMidpoint = 0.5;
|
||||
const double _kCheckDuration = 200.0;
|
||||
const sky.Color _kUncheckedColor = const sky.Color(0x8A000000);
|
||||
// TODO(jackson): This should change colors with the theme
|
||||
sky.Color _kCheckedColor = colors.Purple[500];
|
||||
const double _kEdgeSize = 20.0;
|
||||
const double _kEdgeRadius = 1.0;
|
||||
|
||||
class Checkbox extends AnimatedComponent {
|
||||
class Checkbox extends Toggleable {
|
||||
|
||||
Checkbox({
|
||||
Object key,
|
||||
this.checked,
|
||||
this.onChanged
|
||||
}) : super(key: key) {
|
||||
_checkedAnimation = new AnimatedValue(checked ? 1.0 : 0.0);
|
||||
}
|
||||
bool value,
|
||||
ValueChanged onChanged
|
||||
}) : super(key: key, value: value, onChanged: onChanged);
|
||||
|
||||
bool checked;
|
||||
AnimatedValue _checkedAnimation;
|
||||
ValueChanged onChanged;
|
||||
Size get size => const Size(_kEdgeSize + 2.0, _kEdgeSize + 2.0);
|
||||
|
||||
void syncFields(Checkbox source) {
|
||||
onChanged = source.onChanged;
|
||||
if (checked != source.checked) {
|
||||
checked = source.checked;
|
||||
double targetValue = checked ? 1.0 : 0.0;
|
||||
double difference = (_checkedAnimation.value - targetValue).abs();
|
||||
if (difference > 0) {
|
||||
double duration = difference * _kCheckDuration;
|
||||
_checkedAnimation.stop();
|
||||
Curve curve;
|
||||
if (targetValue > _checkedAnimation.value) {
|
||||
curve = easeIn;
|
||||
} else {
|
||||
curve = easeOut;
|
||||
}
|
||||
_checkedAnimation.animateTo(targetValue, duration, curve: curve);
|
||||
}
|
||||
}
|
||||
super.syncFields(source);
|
||||
}
|
||||
void customPaintCallback(sky.Canvas canvas, Size size) {
|
||||
// Choose a color between grey and the theme color
|
||||
sky.Paint paint = new sky.Paint()..strokeWidth = 2.0
|
||||
..color = _kUncheckedColor;
|
||||
|
||||
void _handleClick(sky.Event e) {
|
||||
onChanged(!checked);
|
||||
}
|
||||
// The rrect contracts slightly during the animation
|
||||
double inset = 2.0 - (toggleAnimation.value - _kMidpoint).abs() * 2.0;
|
||||
sky.Rect rect = new sky.Rect.fromLTRB(inset, inset, _kEdgeSize - inset, _kEdgeSize - inset);
|
||||
sky.RRect rrect = new sky.RRect()..setRectXY(rect, _kEdgeRadius, _kEdgeRadius);
|
||||
|
||||
UINode build() {
|
||||
return new EventListenerNode(
|
||||
new Container(
|
||||
margin: const EdgeDims.symmetric(horizontal: 5.0),
|
||||
width: _kEdgeSize + 2.0,
|
||||
height: _kEdgeSize + 2.0,
|
||||
child: new CustomPaint(
|
||||
token: _checkedAnimation.value,
|
||||
callback: (sky.Canvas canvas, Size size) {
|
||||
// Choose a color between grey and the theme color
|
||||
sky.Paint paint = new sky.Paint()..strokeWidth = 2.0
|
||||
..color = _kUncheckedColor;
|
||||
// Outline of the empty rrect
|
||||
paint.setStyle(sky.PaintingStyle.stroke);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// The rrect contracts slightly during the animation
|
||||
double inset = 2.0 - (_checkedAnimation.value - _kMidpoint).abs() * 2.0;
|
||||
sky.Rect rect = new sky.Rect.fromLTRB(inset, inset, _kEdgeSize - inset, _kEdgeSize - inset);
|
||||
sky.RRect rrect = new sky.RRect()..setRectXY(rect, _kEdgeRadius, _kEdgeRadius);
|
||||
|
||||
|
||||
// Outline of the empty rrect
|
||||
paint.setStyle(sky.PaintingStyle.stroke);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// Radial gradient that changes size
|
||||
if (_checkedAnimation.value > 0) {
|
||||
paint.setStyle(sky.PaintingStyle.fill);
|
||||
paint.setShader(
|
||||
new sky.Gradient.radial(
|
||||
new Point(_kEdgeSize / 2.0, _kEdgeSize / 2.0),
|
||||
_kEdgeSize * (_kMidpoint - _checkedAnimation.value) * 8.0,
|
||||
[const sky.Color(0x00000000), _kUncheckedColor],
|
||||
[0.0, 1.0]
|
||||
)
|
||||
);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
}
|
||||
|
||||
if (_checkedAnimation.value > _kMidpoint) {
|
||||
double t = (_checkedAnimation.value - _kMidpoint) / (1.0 - _kMidpoint);
|
||||
|
||||
// Solid filled rrect
|
||||
paint.setStyle(sky.PaintingStyle.strokeAndFill);
|
||||
paint.color = new Color.fromARGB((t * 255).floor(),
|
||||
_kCheckedColor.red,
|
||||
_kCheckedColor.green,
|
||||
_kCheckedColor.blue);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// White inner check
|
||||
paint.color = const sky.Color(0xFFFFFFFF);
|
||||
paint.setStyle(sky.PaintingStyle.stroke);
|
||||
sky.Path path = new sky.Path();
|
||||
sky.Point start = new sky.Point(_kEdgeSize * 0.2, _kEdgeSize * 0.5);
|
||||
sky.Point mid = new sky.Point(_kEdgeSize * 0.4, _kEdgeSize * 0.7);
|
||||
sky.Point end = new sky.Point(_kEdgeSize * 0.8, _kEdgeSize * 0.3);
|
||||
Point lerp(Point p1, Point p2, double t)
|
||||
=> new Point(p1.x * (1.0 - t) + p2.x * t, p1.y * (1.0 - t) + p2.y * t);
|
||||
sky.Point drawStart = lerp(start, mid, 1.0 - t);
|
||||
sky.Point drawEnd = lerp(mid, end, t);
|
||||
path.moveTo(drawStart.x, drawStart.y);
|
||||
path.lineTo(mid.x, mid.y);
|
||||
path.lineTo(drawEnd.x, drawEnd.y);
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
}
|
||||
// Radial gradient that changes size
|
||||
if (toggleAnimation.value > 0) {
|
||||
paint.setStyle(sky.PaintingStyle.fill);
|
||||
paint.setShader(
|
||||
new sky.Gradient.radial(
|
||||
new Point(_kEdgeSize / 2.0, _kEdgeSize / 2.0),
|
||||
_kEdgeSize * (_kMidpoint - toggleAnimation.value) * 8.0,
|
||||
[const sky.Color(0x00000000), _kUncheckedColor]
|
||||
)
|
||||
),
|
||||
onGestureTap: _handleClick
|
||||
);
|
||||
}
|
||||
);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
}
|
||||
|
||||
if (toggleAnimation.value > _kMidpoint) {
|
||||
double t = (toggleAnimation.value - _kMidpoint) / (1.0 - _kMidpoint);
|
||||
|
||||
// Solid filled rrect
|
||||
paint.setStyle(sky.PaintingStyle.strokeAndFill);
|
||||
paint.color = new Color.fromARGB((t * 255).floor(),
|
||||
_kCheckedColor.red,
|
||||
_kCheckedColor.green,
|
||||
_kCheckedColor.blue);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// White inner check
|
||||
paint.color = const sky.Color(0xFFFFFFFF);
|
||||
paint.setStyle(sky.PaintingStyle.stroke);
|
||||
sky.Path path = new sky.Path();
|
||||
sky.Point start = new sky.Point(_kEdgeSize * 0.2, _kEdgeSize * 0.5);
|
||||
sky.Point mid = new sky.Point(_kEdgeSize * 0.4, _kEdgeSize * 0.7);
|
||||
sky.Point end = new sky.Point(_kEdgeSize * 0.8, _kEdgeSize * 0.3);
|
||||
Point lerp(Point p1, Point p2, double t)
|
||||
=> new Point(p1.x * (1.0 - t) + p2.x * t, p1.y * (1.0 - t) + p2.y * t);
|
||||
sky.Point drawStart = lerp(start, mid, 1.0 - t);
|
||||
sky.Point drawEnd = lerp(mid, end, t);
|
||||
path.moveTo(drawStart.x, drawStart.y);
|
||||
path.lineTo(mid.x, mid.y);
|
||||
path.lineTo(drawEnd.x, drawEnd.y);
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
sdk/lib/widgets/switch.dart
Normal file
69
sdk/lib/widgets/switch.dart
Normal file
@ -0,0 +1,69 @@
|
||||
// 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:sky' as sky;
|
||||
|
||||
import 'package:sky/theme2/colors.dart' as colors;
|
||||
import 'package:sky/theme2/shadows.dart';
|
||||
|
||||
import '../painting/shadows.dart';
|
||||
import '../rendering/box.dart';
|
||||
import 'basic.dart';
|
||||
import 'toggleable.dart';
|
||||
|
||||
// TODO(jackson): This should change colors with the theme
|
||||
sky.Color _kThumbOnColor = colors.Purple[500];
|
||||
const sky.Color _kThumbOffColor = const sky.Color(0xFFFAFAFA);
|
||||
sky.Color _kTrackOnColor = new sky.Color(_kThumbOnColor.value & (0x80 << 24));
|
||||
const sky.Color _kTrackOffColor = const sky.Color(0x42000000);
|
||||
const double _kSwitchWidth = 35.0;
|
||||
const double _kThumbRadius = 10.0;
|
||||
const double _kSwitchHeight = _kThumbRadius * 2.0;
|
||||
const double _kTrackHeight = 14.0;
|
||||
const double _kTrackRadius = _kTrackHeight / 2.0;
|
||||
const double _kTrackWidth = _kSwitchWidth - (_kThumbRadius - _kTrackRadius) * 2.0;
|
||||
|
||||
class Switch extends Toggleable {
|
||||
// TODO(jackson): Hit-test the switch so that it can respond to both taps and swipe gestures
|
||||
|
||||
Switch({
|
||||
Object key,
|
||||
bool value,
|
||||
ValueChanged onChanged
|
||||
}) : super(key: key, value: value, onChanged: onChanged);
|
||||
|
||||
Size get size => const Size(_kSwitchWidth + 2.0, _kSwitchHeight + 2.0);
|
||||
|
||||
void customPaintCallback(sky.Canvas canvas, Size size) {
|
||||
sky.Color thumbColor = value ? _kThumbOnColor : _kThumbOffColor;
|
||||
sky.Color trackColor = value ? _kTrackOnColor : _kTrackOffColor;
|
||||
|
||||
// Draw the track rrect
|
||||
sky.Paint paint = new sky.Paint()..color = trackColor;
|
||||
paint.setStyle(sky.PaintingStyle.fill);
|
||||
sky.Rect rect = new sky.Rect.fromLTRB(
|
||||
0.0,
|
||||
_kSwitchHeight / 2.0 - _kTrackHeight / 2.0,
|
||||
_kTrackWidth,
|
||||
_kSwitchHeight / 2.0 + _kTrackHeight / 2.0
|
||||
);
|
||||
sky.RRect rrect = new sky.RRect()..setRectXY(rect, _kTrackRadius, _kTrackRadius);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// Draw the raised thumb with a shadow
|
||||
paint.color = thumbColor;
|
||||
var builder = new ShadowDrawLooperBuilder();
|
||||
for (BoxShadow boxShadow in shadows[1])
|
||||
builder.addShadow(boxShadow.offset, boxShadow.color, boxShadow.blur);
|
||||
paint.setDrawLooper(builder.build());
|
||||
|
||||
// The thumb contracts slightly during the animation
|
||||
double inset = 2.0 - (toggleAnimation.value - 0.5).abs() * 2.0;
|
||||
Point thumbPos = new Point(
|
||||
_kTrackRadius + toggleAnimation.value * (_kTrackWidth - _kTrackRadius * 2),
|
||||
_kSwitchHeight / 2.0
|
||||
);
|
||||
canvas.drawCircle(thumbPos.x, thumbPos.y, _kThumbRadius - inset, paint);
|
||||
}
|
||||
}
|
||||
74
sdk/lib/widgets/toggleable.dart
Normal file
74
sdk/lib/widgets/toggleable.dart
Normal file
@ -0,0 +1,74 @@
|
||||
// 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:sky' as sky;
|
||||
|
||||
import 'animated_component.dart';
|
||||
import 'basic.dart';
|
||||
import '../framework/animation/animated_value.dart';
|
||||
import '../framework/animation/curves.dart';
|
||||
|
||||
typedef void ValueChanged(value);
|
||||
|
||||
const double _kCheckDuration = 200.0;
|
||||
|
||||
abstract class Toggleable extends AnimatedComponent {
|
||||
|
||||
Toggleable({
|
||||
Object key,
|
||||
this.value,
|
||||
this.onChanged
|
||||
}) : super(key: key) {
|
||||
toggleAnimation = new AnimatedValue(value ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
bool value;
|
||||
AnimatedValue toggleAnimation;
|
||||
ValueChanged onChanged;
|
||||
|
||||
void syncFields(Toggleable source) {
|
||||
onChanged = source.onChanged;
|
||||
if (value != source.value) {
|
||||
value = source.value;
|
||||
double targetValue = value ? 1.0 : 0.0;
|
||||
double difference = (toggleAnimation.value - targetValue).abs();
|
||||
if (difference > 0) {
|
||||
toggleAnimation.stop();
|
||||
double t = difference * duration;
|
||||
Curve curve = targetValue > toggleAnimation.value ? curveUp : curveDown;
|
||||
toggleAnimation.animateTo(targetValue, t, curve: curve);
|
||||
}
|
||||
}
|
||||
super.syncFields(source);
|
||||
}
|
||||
|
||||
void _handleClick(sky.Event e) {
|
||||
onChanged(!value);
|
||||
}
|
||||
|
||||
// Override these methods to draw yourself
|
||||
void customPaintCallback(sky.Canvas canvas, Size size) {
|
||||
assert(false);
|
||||
}
|
||||
Size get size => const Size.zero;
|
||||
EdgeDims get margin => const EdgeDims.symmetric(horizontal: 5.0);
|
||||
double get duration => 200.0;
|
||||
Curve get curveUp => easeIn;
|
||||
Curve get curveDown => easeOut;
|
||||
|
||||
UINode build() {
|
||||
return new EventListenerNode(
|
||||
new Container(
|
||||
margin: margin,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: new CustomPaint(
|
||||
token: toggleAnimation.value,
|
||||
callback: customPaintCallback
|
||||
)
|
||||
),
|
||||
onGestureTap: _handleClick
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user