mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Animate checkboxes. I based it on the dialer app. Could probably use more improvement, but I think this is an improvement on what we are doing currently.
R=abarth@chromium.org, abarth Review URL: https://codereview.chromium.org/1182743002.
This commit is contained in:
parent
518ecd24e5
commit
dd1df9f5ed
@ -6,22 +6,54 @@ import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/framework/theme2/colors.dart' as colors;
|
||||
|
||||
import '../animation/animated_value.dart';
|
||||
import '../animation/curves.dart';
|
||||
import '../rendering/box.dart';
|
||||
import 'button_base.dart';
|
||||
import 'animated_component.dart';
|
||||
import 'wrappers.dart';
|
||||
|
||||
typedef void ValueChanged(value);
|
||||
|
||||
class Checkbox extends ButtonBase {
|
||||
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;
|
||||
|
||||
Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key);
|
||||
class Checkbox extends AnimatedComponent {
|
||||
|
||||
Checkbox({
|
||||
Object key,
|
||||
this.checked,
|
||||
this.onChanged
|
||||
}) : super(key: key) {
|
||||
_checkedAnimation = new AnimatedValue(checked ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
bool checked;
|
||||
AnimatedValue _checkedAnimation;
|
||||
ValueChanged onChanged;
|
||||
|
||||
void syncFields(Checkbox source) {
|
||||
checked = source.checked;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -29,37 +61,68 @@ class Checkbox extends ButtonBase {
|
||||
onChanged(!checked);
|
||||
}
|
||||
|
||||
UINode buildContent() {
|
||||
// TODO(jackson): This should change colors with the theme
|
||||
sky.Color color = highlight ? colors.Purple[500] : const sky.Color(0x8A000000);
|
||||
const double kEdgeSize = 20.0;
|
||||
const double kEdgeRadius = 1.0;
|
||||
UINode build() {
|
||||
return new EventListenerNode(
|
||||
new Container(
|
||||
margin: const EdgeDims.symmetric(horizontal: 5.0),
|
||||
width: kEdgeSize + 2.0,
|
||||
height: kEdgeSize + 2.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;
|
||||
|
||||
sky.Paint paint = new sky.Paint()..color = color
|
||||
..strokeWidth = 2.0;
|
||||
// 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);
|
||||
|
||||
// Draw the outer rrect
|
||||
paint.setStyle(checked ? sky.PaintingStyle.strokeAndFill : sky.PaintingStyle.stroke);
|
||||
sky.Rect rect = new sky.Rect.fromLTRB(0.0, 0.0, kEdgeSize, kEdgeSize);
|
||||
sky.RRect rrect = new sky.RRect()..setRectXY(rect, kEdgeRadius, kEdgeRadius);
|
||||
|
||||
// Outline of the empty rrect
|
||||
paint.setStyle(sky.PaintingStyle.stroke);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// Draw the inner check
|
||||
if (checked) {
|
||||
// TODO(jackson): Use the theme color
|
||||
// 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();
|
||||
path.moveTo(kEdgeSize * 0.2, kEdgeSize * 0.5);
|
||||
path.lineTo(kEdgeSize * 0.4, kEdgeSize * 0.7);
|
||||
path.lineTo(kEdgeSize * 0.8, kEdgeSize * 0.3);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,19 +49,21 @@ class DecoratedBox extends OneChildRenderObjectWrapper {
|
||||
|
||||
}
|
||||
|
||||
// TODO(jackson) need a mechanism for marking the RenderCustomPaint as needing paint
|
||||
class CustomPaint extends OneChildRenderObjectWrapper {
|
||||
|
||||
CustomPaint({ this.callback, UINode child, Object key })
|
||||
CustomPaint({ this.callback, this.token, UINode child, Object key })
|
||||
: super(child: child, key: key);
|
||||
|
||||
RenderCustomPaint get root { RenderCustomPaint result = super.root; return result; }
|
||||
final CustomPaintCallback callback;
|
||||
final dynamic token; // set this to be repainted automatically when the token changes
|
||||
|
||||
RenderCustomPaint createNode() => new RenderCustomPaint(callback: callback);
|
||||
|
||||
void syncRenderObject(CustomPaint old) {
|
||||
super.syncRenderObject(old);
|
||||
if (old != null && old.token != token)
|
||||
root.markNeedsPaint();
|
||||
root.callback = callback;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user