mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Make Switch a standalone class with a custom RenderObject
This changes Switch from being a subclass of the Toggleable widget to being a standalone Component with a custom RenderObject. This is to enable adding Switch-specific radial reaction animations in a subsequent patch. The CustomPaint logic that Toggleable was using does not provide a nice way for a class and its subclass to both participate in deciding to repaint.
This commit is contained in:
parent
456e17d033
commit
445f4ee8cd
35
packages/flutter/example/widgets/big_switch.dart
Normal file
35
packages/flutter/example/widgets/big_switch.dart
Normal file
@ -0,0 +1,35 @@
|
||||
// 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 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/switch.dart';
|
||||
import 'package:sky/theme/colors.dart';
|
||||
import 'package:vector_math/vector_math.dart';
|
||||
|
||||
class BigSwitchApp extends App {
|
||||
bool _value = false;
|
||||
|
||||
void _handleOnChanged(bool value) {
|
||||
setState(() {
|
||||
_value = value;
|
||||
});
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
Matrix4 scale = new Matrix4.identity();
|
||||
scale.scale(5.0, 5.0);
|
||||
return new Container(
|
||||
child: new Switch(value: _value, onChanged: _handleOnChanged),
|
||||
padding: new EdgeDims.all(5.0),
|
||||
transform: scale,
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: Teal[600]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(new BigSwitchApp());
|
||||
}
|
||||
@ -24,6 +24,7 @@ export 'package:sky/rendering/flex.dart' show FlexDirection, FlexJustifyContent,
|
||||
export 'package:sky/rendering/object.dart' show Point, Offset, Size, Rect, Color, Paint, Path;
|
||||
export 'package:sky/widgets/widget.dart' show Key, GlobalKey, Widget, Component, StatefulComponent, App, runApp, Listener, ParentDataNode;
|
||||
|
||||
typedef void ValueChanged(bool);
|
||||
|
||||
// PAINTING NODES
|
||||
|
||||
|
||||
@ -4,14 +4,18 @@
|
||||
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/animation/animated_value.dart';
|
||||
import 'package:sky/animation/animation_performance.dart';
|
||||
import 'package:sky/animation/curves.dart';
|
||||
import 'package:sky/painting/shadows.dart';
|
||||
import 'package:sky/rendering/box.dart';
|
||||
import 'package:sky/rendering/object.dart';
|
||||
import 'package:sky/theme/shadows.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/theme.dart';
|
||||
import 'package:sky/widgets/toggleable.dart';
|
||||
import 'package:sky/widgets/widget.dart';
|
||||
|
||||
export 'package:sky/widgets/toggleable.dart' show ValueChanged;
|
||||
export 'package:sky/widgets/basic.dart' show ValueChanged;
|
||||
|
||||
const sky.Color _kThumbOffColor = const sky.Color(0xFFFAFAFA);
|
||||
const sky.Color _kTrackOffColor = const sky.Color(0x42000000);
|
||||
@ -20,52 +24,136 @@ 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;
|
||||
const double _kTrackWidth =
|
||||
_kSwitchWidth - (_kThumbRadius - _kTrackRadius) * 2.0;
|
||||
const Duration _kCheckDuration = const Duration(milliseconds: 200);
|
||||
const Size _kSwitchSize = const Size(_kSwitchWidth + 2.0, _kSwitchHeight + 2.0);
|
||||
|
||||
class Switch extends Toggleable {
|
||||
// TODO(jackson): Hit-test the switch so that it can respond to both taps and swipe gestures
|
||||
class Switch extends Component {
|
||||
Switch({Key key, this.value, this.onChanged}) : super(key: key);
|
||||
|
||||
Switch({
|
||||
Key key,
|
||||
bool value,
|
||||
ValueChanged onChanged
|
||||
}) : super(key: key, value: value, onChanged: onChanged);
|
||||
final bool value;
|
||||
final ValueChanged onChanged;
|
||||
|
||||
Size get size => const Size(_kSwitchWidth + 2.0, _kSwitchHeight + 2.0);
|
||||
Widget build() {
|
||||
return new _SwitchWrapper(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
thumbColor: Theme.of(this).accentColor);
|
||||
}
|
||||
}
|
||||
|
||||
void customPaintCallback(sky.Canvas canvas, Size size) {
|
||||
// This wrapper class exists only because Switch needs to be a Component in
|
||||
// order to get an accent color from a Theme but Components do not know how to
|
||||
// host RenderObjects.
|
||||
class _SwitchWrapper extends LeafRenderObjectWrapper {
|
||||
_SwitchWrapper({Key key, this.value, this.onChanged, this.thumbColor})
|
||||
: super(key: key);
|
||||
|
||||
final bool value;
|
||||
final ValueChanged onChanged;
|
||||
final Color thumbColor;
|
||||
|
||||
_RenderSwitch get root => super.root;
|
||||
_RenderSwitch createNode() => new _RenderSwitch(
|
||||
value: value, thumbColor: thumbColor, onChanged: onChanged);
|
||||
|
||||
void syncRenderObject(_SwitchWrapper old) {
|
||||
super.syncRenderObject(old);
|
||||
root.value = value;
|
||||
root.onChanged = onChanged;
|
||||
root.thumbColor = thumbColor;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderSwitch extends RenderConstrainedBox {
|
||||
_RenderSwitch(
|
||||
{bool value, Color thumbColor: _kThumbOffColor, ValueChanged onChanged})
|
||||
: _value = value,
|
||||
_thumbColor = thumbColor,
|
||||
_onChanged = onChanged,
|
||||
super(additionalConstraints: new BoxConstraints.tight(_kSwitchSize)) {
|
||||
_performance = new AnimationPerformance()
|
||||
..variable = _position
|
||||
..duration = _kCheckDuration
|
||||
..progress = _value ? 1.0 : 0.0
|
||||
..addListener(markNeedsPaint);
|
||||
}
|
||||
|
||||
void handleEvent(sky.Event event, BoxHitTestEntry entry) {
|
||||
if (event is sky.GestureEvent &&
|
||||
event.type == 'gesturetap') _onChanged(!_value);
|
||||
}
|
||||
|
||||
bool _value;
|
||||
bool get value => _value;
|
||||
|
||||
void set value(bool value) {
|
||||
if (value == _value) return;
|
||||
_value = value;
|
||||
// TODO(abarth): Setting the curve on the position means there's a
|
||||
// discontinuity when we reverse the timeline.
|
||||
if (value) {
|
||||
_position.curve = easeIn;
|
||||
_performance.play();
|
||||
} else {
|
||||
_position.curve = easeOut;
|
||||
_performance.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
Color _thumbColor;
|
||||
Color get thumbColor => _thumbColor;
|
||||
|
||||
void set thumbColor(Color value) {
|
||||
if (value == _thumbColor) return;
|
||||
_thumbColor = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
ValueChanged _onChanged;
|
||||
ValueChanged get onChanged => _onChanged;
|
||||
|
||||
void set onChanged(ValueChanged onChanged) {
|
||||
_onChanged = onChanged;
|
||||
}
|
||||
|
||||
final AnimatedValue<double> _position =
|
||||
new AnimatedValue<double>(0.0, end: 1.0);
|
||||
|
||||
AnimationPerformance _performance;
|
||||
|
||||
void paint(PaintingCanvas canvas, Offset offset) {
|
||||
sky.Color thumbColor = _kThumbOffColor;
|
||||
sky.Color trackColor = _kTrackOffColor;
|
||||
if (value) {
|
||||
thumbColor = Theme.of(this).accentColor;
|
||||
trackColor = new sky.Color(thumbColor.value & 0x80FFFFFF);
|
||||
if (_value) {
|
||||
thumbColor = _thumbColor;
|
||||
trackColor = new sky.Color(_thumbColor.value & 0x80FFFFFF);
|
||||
}
|
||||
|
||||
// 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);
|
||||
sky.Rect rect = new sky.Rect.fromLTWH(offset.dx,
|
||||
offset.dy + _kSwitchHeight / 2.0 - _kTrackHeight / 2.0, _kTrackWidth,
|
||||
_kTrackHeight);
|
||||
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);
|
||||
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 - (position.value - 0.5).abs() * 2.0;
|
||||
Point thumbPos = new Point(
|
||||
_kTrackRadius + position.value * (_kTrackWidth - _kTrackRadius * 2),
|
||||
_kSwitchHeight / 2.0
|
||||
);
|
||||
double inset = 2.0 - (_position.value - 0.5).abs() * 2.0;
|
||||
Point thumbPos = new Point(offset.dx +
|
||||
_kTrackRadius +
|
||||
_position.value * (_kTrackWidth - _kTrackRadius * 2),
|
||||
offset.dy + _kSwitchHeight / 2.0);
|
||||
canvas.drawCircle(thumbPos, _kThumbRadius - inset, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import 'package:sky/animation/curves.dart';
|
||||
import 'package:sky/widgets/animated_component.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
|
||||
typedef void ValueChanged(bool value);
|
||||
export 'package:sky/widgets/basic.dart' show ValueChanged;
|
||||
|
||||
const Duration _kCheckDuration = const Duration(milliseconds: 200);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user