Custom switch images are nutty (#4852)
Rather than requiring the developer to specify a full Decoration, we now just take an ImageProvider for the thumb image. Also, fix ImageFit.scaleDown to actually work. Fixes #4571 Fixes #4673
This commit is contained in:
parent
d7a4a54b36
commit
fc711a10cd
@ -168,11 +168,14 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
|
|||||||
child: new Row(
|
child: new Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new Switch(value: switchValue, onChanged: (bool value) {
|
new Switch(
|
||||||
setState(() {
|
value: switchValue,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
switchValue = value;
|
switchValue = value;
|
||||||
});
|
});
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
// Disabled switches
|
// Disabled switches
|
||||||
new Switch(value: true, onChanged: null),
|
new Switch(value: true, onChanged: null),
|
||||||
new Switch(value: false, onChanged: null)
|
new Switch(value: false, onChanged: null)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
@ -46,8 +47,8 @@ class Switch extends StatelessWidget {
|
|||||||
@required this.value,
|
@required this.value,
|
||||||
@required this.onChanged,
|
@required this.onChanged,
|
||||||
this.activeColor,
|
this.activeColor,
|
||||||
this.activeThumbDecoration,
|
this.activeThumbImage,
|
||||||
this.inactiveThumbDecoration
|
this.inactiveThumbImage
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Whether this switch is on or off.
|
/// Whether this switch is on or off.
|
||||||
@ -67,15 +68,11 @@ class Switch extends StatelessWidget {
|
|||||||
/// Defaults to accent color of the current [Theme].
|
/// Defaults to accent color of the current [Theme].
|
||||||
final Color activeColor;
|
final Color activeColor;
|
||||||
|
|
||||||
/// A decoration to use for the thumb of this switch when the switch is on.
|
/// An image to use on the thumb of this switch when the switch is on.
|
||||||
///
|
final ImageProvider activeThumbImage;
|
||||||
/// Defaults to a circular piece of material.
|
|
||||||
final Decoration activeThumbDecoration;
|
|
||||||
|
|
||||||
/// A decoration to use for the thumb of this switch when the switch is off.
|
/// An image to use on the thumb of this switch when the switch is off.
|
||||||
///
|
final ImageProvider inactiveThumbImage;
|
||||||
/// Defaults to a circular piece of material.
|
|
||||||
final Decoration inactiveThumbDecoration;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -100,8 +97,8 @@ class Switch extends StatelessWidget {
|
|||||||
value: value,
|
value: value,
|
||||||
activeColor: activeThumbColor,
|
activeColor: activeThumbColor,
|
||||||
inactiveColor: inactiveThumbColor,
|
inactiveColor: inactiveThumbColor,
|
||||||
activeThumbDecoration: activeThumbDecoration,
|
activeThumbImage: activeThumbImage,
|
||||||
inactiveThumbDecoration: inactiveThumbDecoration,
|
inactiveThumbImage: inactiveThumbImage,
|
||||||
activeTrackColor: activeTrackColor,
|
activeTrackColor: activeTrackColor,
|
||||||
inactiveTrackColor: inactiveTrackColor,
|
inactiveTrackColor: inactiveTrackColor,
|
||||||
configuration: createLocalImageConfiguration(context),
|
configuration: createLocalImageConfiguration(context),
|
||||||
@ -124,8 +121,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
|||||||
this.value,
|
this.value,
|
||||||
this.activeColor,
|
this.activeColor,
|
||||||
this.inactiveColor,
|
this.inactiveColor,
|
||||||
this.activeThumbDecoration,
|
this.activeThumbImage,
|
||||||
this.inactiveThumbDecoration,
|
this.inactiveThumbImage,
|
||||||
this.activeTrackColor,
|
this.activeTrackColor,
|
||||||
this.inactiveTrackColor,
|
this.inactiveTrackColor,
|
||||||
this.configuration,
|
this.configuration,
|
||||||
@ -135,8 +132,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
|||||||
final bool value;
|
final bool value;
|
||||||
final Color activeColor;
|
final Color activeColor;
|
||||||
final Color inactiveColor;
|
final Color inactiveColor;
|
||||||
final Decoration activeThumbDecoration;
|
final ImageProvider activeThumbImage;
|
||||||
final Decoration inactiveThumbDecoration;
|
final ImageProvider inactiveThumbImage;
|
||||||
final Color activeTrackColor;
|
final Color activeTrackColor;
|
||||||
final Color inactiveTrackColor;
|
final Color inactiveTrackColor;
|
||||||
final ImageConfiguration configuration;
|
final ImageConfiguration configuration;
|
||||||
@ -147,8 +144,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
|||||||
value: value,
|
value: value,
|
||||||
activeColor: activeColor,
|
activeColor: activeColor,
|
||||||
inactiveColor: inactiveColor,
|
inactiveColor: inactiveColor,
|
||||||
activeThumbDecoration: activeThumbDecoration,
|
activeThumbImage: activeThumbImage,
|
||||||
inactiveThumbDecoration: inactiveThumbDecoration,
|
inactiveThumbImage: inactiveThumbImage,
|
||||||
activeTrackColor: activeTrackColor,
|
activeTrackColor: activeTrackColor,
|
||||||
inactiveTrackColor: inactiveTrackColor,
|
inactiveTrackColor: inactiveTrackColor,
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
@ -161,8 +158,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
|||||||
..value = value
|
..value = value
|
||||||
..activeColor = activeColor
|
..activeColor = activeColor
|
||||||
..inactiveColor = inactiveColor
|
..inactiveColor = inactiveColor
|
||||||
..activeThumbDecoration = activeThumbDecoration
|
..activeThumbImage = activeThumbImage
|
||||||
..inactiveThumbDecoration = inactiveThumbDecoration
|
..inactiveThumbImage = inactiveThumbImage
|
||||||
..activeTrackColor = activeTrackColor
|
..activeTrackColor = activeTrackColor
|
||||||
..inactiveTrackColor = inactiveTrackColor
|
..inactiveTrackColor = inactiveTrackColor
|
||||||
..configuration = configuration
|
..configuration = configuration
|
||||||
@ -182,14 +179,14 @@ class _RenderSwitch extends RenderToggleable {
|
|||||||
bool value,
|
bool value,
|
||||||
Color activeColor,
|
Color activeColor,
|
||||||
Color inactiveColor,
|
Color inactiveColor,
|
||||||
Decoration activeThumbDecoration,
|
ImageProvider activeThumbImage,
|
||||||
Decoration inactiveThumbDecoration,
|
ImageProvider inactiveThumbImage,
|
||||||
Color activeTrackColor,
|
Color activeTrackColor,
|
||||||
Color inactiveTrackColor,
|
Color inactiveTrackColor,
|
||||||
ImageConfiguration configuration,
|
ImageConfiguration configuration,
|
||||||
ValueChanged<bool> onChanged
|
ValueChanged<bool> onChanged
|
||||||
}) : _activeThumbDecoration = activeThumbDecoration,
|
}) : _activeThumbImage = activeThumbImage,
|
||||||
_inactiveThumbDecoration = inactiveThumbDecoration,
|
_inactiveThumbImage = inactiveThumbImage,
|
||||||
_activeTrackColor = activeTrackColor,
|
_activeTrackColor = activeTrackColor,
|
||||||
_inactiveTrackColor = inactiveTrackColor,
|
_inactiveTrackColor = inactiveTrackColor,
|
||||||
_configuration = configuration,
|
_configuration = configuration,
|
||||||
@ -206,21 +203,21 @@ class _RenderSwitch extends RenderToggleable {
|
|||||||
..onEnd = _handleDragEnd;
|
..onEnd = _handleDragEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
Decoration get activeThumbDecoration => _activeThumbDecoration;
|
ImageProvider get activeThumbImage => _activeThumbImage;
|
||||||
Decoration _activeThumbDecoration;
|
ImageProvider _activeThumbImage;
|
||||||
set activeThumbDecoration(Decoration value) {
|
set activeThumbImage(ImageProvider value) {
|
||||||
if (value == _activeThumbDecoration)
|
if (value == _activeThumbImage)
|
||||||
return;
|
return;
|
||||||
_activeThumbDecoration = value;
|
_activeThumbImage = value;
|
||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
Decoration get inactiveThumbDecoration => _inactiveThumbDecoration;
|
ImageProvider get inactiveThumbImage => _inactiveThumbImage;
|
||||||
Decoration _inactiveThumbDecoration;
|
ImageProvider _inactiveThumbImage;
|
||||||
set inactiveThumbDecoration(Decoration value) {
|
set inactiveThumbImage(ImageProvider value) {
|
||||||
if (value == _inactiveThumbDecoration)
|
if (value == _inactiveThumbImage)
|
||||||
return;
|
return;
|
||||||
_inactiveThumbDecoration = value;
|
_inactiveThumbImage = value;
|
||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,16 +292,29 @@ class _RenderSwitch extends RenderToggleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color _cachedThumbColor;
|
Color _cachedThumbColor;
|
||||||
|
ImageProvider _cachedThumbImage;
|
||||||
BoxPainter _cachedThumbPainter;
|
BoxPainter _cachedThumbPainter;
|
||||||
|
|
||||||
BoxDecoration _createDefaultThumbDecoration(Color color) {
|
BoxDecoration _createDefaultThumbDecoration(Color color, ImageProvider image) {
|
||||||
return new BoxDecoration(
|
return new BoxDecoration(
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
|
backgroundImage: image == null ? null : new BackgroundImage(image: image),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: kElevationToShadow[1]
|
boxShadow: kElevationToShadow[1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isPainting = false;
|
||||||
|
|
||||||
|
void _handleDecorationChanged() {
|
||||||
|
// If the image decoration is available synchronously, we'll get called here
|
||||||
|
// during paint. There's no reason to mark ourselves as needing paint if we
|
||||||
|
// are already in the middle of painting. (In fact, doing so would trigger
|
||||||
|
// an assert).
|
||||||
|
if (!_isPainting)
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
final Canvas canvas = context.canvas;
|
final Canvas canvas = context.canvas;
|
||||||
@ -334,27 +344,28 @@ class _RenderSwitch extends RenderToggleable {
|
|||||||
|
|
||||||
paintRadialReaction(canvas, offset, thumbPosition);
|
paintRadialReaction(canvas, offset, thumbPosition);
|
||||||
|
|
||||||
BoxPainter thumbPainter;
|
try {
|
||||||
if (_inactiveThumbDecoration == null && _activeThumbDecoration == null) {
|
_isPainting = true;
|
||||||
|
BoxPainter thumbPainter;
|
||||||
final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentPosition) : inactiveColor;
|
final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentPosition) : inactiveColor;
|
||||||
if (thumbColor != _cachedThumbColor || _cachedThumbPainter == null) {
|
final ImageProvider thumbImage = isActive ? (currentPosition < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage;
|
||||||
|
if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage) {
|
||||||
_cachedThumbColor = thumbColor;
|
_cachedThumbColor = thumbColor;
|
||||||
_cachedThumbPainter = _createDefaultThumbDecoration(thumbColor).createBoxPainter(markNeedsPaint);
|
_cachedThumbImage = thumbImage;
|
||||||
|
_cachedThumbPainter = _createDefaultThumbDecoration(thumbColor, thumbImage).createBoxPainter(_handleDecorationChanged);
|
||||||
}
|
}
|
||||||
thumbPainter = _cachedThumbPainter;
|
thumbPainter = _cachedThumbPainter;
|
||||||
} else {
|
|
||||||
final Decoration startDecoration = _inactiveThumbDecoration ?? _createDefaultThumbDecoration(inactiveColor);
|
|
||||||
final Decoration endDecoration = _activeThumbDecoration ?? _createDefaultThumbDecoration(isActive ? activeTrackColor : inactiveColor);
|
|
||||||
thumbPainter = Decoration.lerp(startDecoration, endDecoration, currentPosition).createBoxPainter(markNeedsPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The thumb contracts slightly during the animation
|
// The thumb contracts slightly during the animation
|
||||||
final double inset = 1.0 - (currentPosition - 0.5).abs() * 2.0;
|
final double inset = 1.0 - (currentPosition - 0.5).abs() * 2.0;
|
||||||
final double radius = _kThumbRadius - inset;
|
final double radius = _kThumbRadius - inset;
|
||||||
thumbPainter.paint(
|
thumbPainter.paint(
|
||||||
canvas,
|
canvas,
|
||||||
thumbPosition.toOffset() + offset - new Offset(radius, radius),
|
thumbPosition.toOffset() + offset - new Offset(radius, radius),
|
||||||
configuration.copyWith(size: new Size.fromRadius(radius))
|
configuration.copyWith(size: new Size.fromRadius(radius))
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
_isPainting = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -864,11 +864,12 @@ void paintImage({
|
|||||||
break;
|
break;
|
||||||
case ImageFit.scaleDown:
|
case ImageFit.scaleDown:
|
||||||
sourceSize = inputSize;
|
sourceSize = inputSize;
|
||||||
destinationSize = outputSize;
|
destinationSize = inputSize;
|
||||||
if (sourceSize.height > destinationSize.height)
|
final double aspectRatio = inputSize.width / inputSize.height;
|
||||||
destinationSize = new Size(sourceSize.width * destinationSize.height / sourceSize.height, sourceSize.height);
|
if (destinationSize.height > outputSize.height)
|
||||||
if (sourceSize.width > destinationSize.width)
|
destinationSize = new Size(outputSize.height * aspectRatio, outputSize.height);
|
||||||
destinationSize = new Size(destinationSize.width, sourceSize.height * destinationSize.width / sourceSize.width);
|
if (destinationSize.width > outputSize.width)
|
||||||
|
destinationSize = new Size(outputSize.width, outputSize.height / aspectRatio);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (centerSlice != null) {
|
if (centerSlice != null) {
|
||||||
|
@ -71,45 +71,4 @@ void main() {
|
|||||||
await tester.tap(find.byKey(switchKey));
|
await tester.tap(find.byKey(switchKey));
|
||||||
expect(value, isTrue);
|
expect(value, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Switch listens to the decorations it paints', (WidgetTester tester) async {
|
|
||||||
TestDecoration activeDecoration = new TestDecoration();
|
|
||||||
TestDecoration inactiveDecoration = new TestDecoration();
|
|
||||||
|
|
||||||
Widget build(bool active, TestDecoration activeDecoration, TestDecoration inactiveDecoration) {
|
|
||||||
return new Material(
|
|
||||||
child: new Center(
|
|
||||||
child: new Switch(
|
|
||||||
value: active,
|
|
||||||
onChanged: null,
|
|
||||||
activeThumbDecoration: activeDecoration,
|
|
||||||
inactiveThumbDecoration: inactiveDecoration
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no build yet
|
|
||||||
expect(activeDecoration.listeners, 0);
|
|
||||||
expect(inactiveDecoration.listeners, 0);
|
|
||||||
|
|
||||||
await tester.pumpWidget(build(false, activeDecoration, inactiveDecoration));
|
|
||||||
expect(activeDecoration.listeners, 0);
|
|
||||||
expect(inactiveDecoration.listeners, 1);
|
|
||||||
|
|
||||||
await tester.pumpWidget(build(true, activeDecoration, inactiveDecoration));
|
|
||||||
// started the animation, but we're on frame 0
|
|
||||||
expect(activeDecoration.listeners, 0);
|
|
||||||
expect(inactiveDecoration.listeners, 2);
|
|
||||||
|
|
||||||
await tester.pump(const Duration(milliseconds: 30)); // slightly into the animation
|
|
||||||
// we're painting some lerped decoration that doesn't exactly match either
|
|
||||||
expect(activeDecoration.listeners, 0);
|
|
||||||
expect(inactiveDecoration.listeners, 2);
|
|
||||||
|
|
||||||
await tester.pump(const Duration(seconds: 1)); // ended animation
|
|
||||||
expect(activeDecoration.listeners, 1);
|
|
||||||
expect(inactiveDecoration.listeners, 2);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user