Reland 39919 CupertinoPicker (#47837)
This commit is contained in:
parent
09b31a4d0d
commit
ae24f18414
@ -24,8 +24,6 @@ const double _kDatePickerPadSize = 12.0;
|
|||||||
// The density of a date picker is different from a generic picker.
|
// The density of a date picker is different from a generic picker.
|
||||||
// Eyeballed from iOS.
|
// Eyeballed from iOS.
|
||||||
const double _kSqueeze = 1.25;
|
const double _kSqueeze = 1.25;
|
||||||
// Considers setting the default background color from the theme, in the future.
|
|
||||||
const Color _kBackgroundColor = CupertinoColors.white;
|
|
||||||
|
|
||||||
const TextStyle _kDefaultPickerTextStyle = TextStyle(
|
const TextStyle _kDefaultPickerTextStyle = TextStyle(
|
||||||
letterSpacing: -0.83,
|
letterSpacing: -0.83,
|
||||||
@ -230,12 +228,11 @@ class CupertinoDatePicker extends StatefulWidget {
|
|||||||
this.maximumYear,
|
this.maximumYear,
|
||||||
this.minuteInterval = 1,
|
this.minuteInterval = 1,
|
||||||
this.use24hFormat = false,
|
this.use24hFormat = false,
|
||||||
this.backgroundColor = _kBackgroundColor,
|
this.backgroundColor,
|
||||||
}) : initialDateTime = initialDateTime ?? DateTime.now(),
|
}) : initialDateTime = initialDateTime ?? DateTime.now(),
|
||||||
assert(mode != null),
|
assert(mode != null),
|
||||||
assert(onDateTimeChanged != null),
|
assert(onDateTimeChanged != null),
|
||||||
assert(minimumYear != null),
|
assert(minimumYear != null),
|
||||||
assert(backgroundColor != null),
|
|
||||||
assert(
|
assert(
|
||||||
minuteInterval > 0 && 60 % minuteInterval == 0,
|
minuteInterval > 0 && 60 % minuteInterval == 0,
|
||||||
'minute interval is not a positive integer factor of 60',
|
'minute interval is not a positive integer factor of 60',
|
||||||
@ -313,7 +310,7 @@ class CupertinoDatePicker extends StatefulWidget {
|
|||||||
|
|
||||||
/// Background color of date picker.
|
/// Background color of date picker.
|
||||||
///
|
///
|
||||||
/// Defaults to [CupertinoColors.white] when null.
|
/// Defaults to null, which disables background painting entirely.
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1242,7 +1239,7 @@ class CupertinoTimerPicker extends StatefulWidget {
|
|||||||
this.minuteInterval = 1,
|
this.minuteInterval = 1,
|
||||||
this.secondInterval = 1,
|
this.secondInterval = 1,
|
||||||
this.alignment = Alignment.center,
|
this.alignment = Alignment.center,
|
||||||
this.backgroundColor = _kBackgroundColor,
|
this.backgroundColor,
|
||||||
@required this.onTimerDurationChanged,
|
@required this.onTimerDurationChanged,
|
||||||
}) : assert(mode != null),
|
}) : assert(mode != null),
|
||||||
assert(onTimerDurationChanged != null),
|
assert(onTimerDurationChanged != null),
|
||||||
@ -1252,7 +1249,6 @@ class CupertinoTimerPicker extends StatefulWidget {
|
|||||||
assert(secondInterval > 0 && 60 % secondInterval == 0),
|
assert(secondInterval > 0 && 60 % secondInterval == 0),
|
||||||
assert(initialTimerDuration.inMinutes % minuteInterval == 0),
|
assert(initialTimerDuration.inMinutes % minuteInterval == 0),
|
||||||
assert(initialTimerDuration.inSeconds % secondInterval == 0),
|
assert(initialTimerDuration.inSeconds % secondInterval == 0),
|
||||||
assert(backgroundColor != null),
|
|
||||||
assert(alignment != null),
|
assert(alignment != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@ -1280,7 +1276,7 @@ class CupertinoTimerPicker extends StatefulWidget {
|
|||||||
|
|
||||||
/// Background color of timer picker.
|
/// Background color of timer picker.
|
||||||
///
|
///
|
||||||
/// Defaults to [CupertinoColors.white] when null.
|
/// Defaults to null, which disables background painting entirely.
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1687,7 +1683,7 @@ class _CupertinoTimerPickerState extends State<CupertinoTimerPicker> {
|
|||||||
child: Align(
|
child: Align(
|
||||||
alignment: widget.alignment,
|
alignment: widget.alignment,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: _kBackgroundColor,
|
color: CupertinoDynamicColor.resolve(widget.backgroundColor, context),
|
||||||
width: totalWidth,
|
width: totalWidth,
|
||||||
height: _kPickerHeight,
|
height: _kPickerHeight,
|
||||||
child: DefaultTextStyle(
|
child: DefaultTextStyle(
|
||||||
|
@ -7,19 +7,23 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'colors.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
/// Color of the 'magnifier' lens border.
|
/// Color of the 'magnifier' lens border.
|
||||||
const Color _kHighlighterBorder = Color(0xFF7F7F7F);
|
const Color _kHighlighterBorder = CupertinoDynamicColor.withBrightness(
|
||||||
const Color _kDefaultBackground = Color(0xFFD2D4DB);
|
color: Color(0x33000000),
|
||||||
|
darkColor: Color(0x33FFFFFF),
|
||||||
|
);
|
||||||
// Eyeballed values comparing with a native picker to produce the right
|
// Eyeballed values comparing with a native picker to produce the right
|
||||||
// curvatures and densities.
|
// curvatures and densities.
|
||||||
const double _kDefaultDiameterRatio = 1.07;
|
const double _kDefaultDiameterRatio = 1.07;
|
||||||
const double _kDefaultPerspective = 0.003;
|
const double _kDefaultPerspective = 0.003;
|
||||||
const double _kSqueeze = 1.45;
|
const double _kSqueeze = 1.45;
|
||||||
/// Opacity fraction value that hides the wheel above and below the 'magnifier'
|
|
||||||
/// lens with the same color as the background.
|
// Opacity fraction value that dims the wheel above and below the "magnifier"
|
||||||
const double _kForegroundScreenOpacityFraction = 0.7;
|
// lens.
|
||||||
|
const double _kOverAndUnderCenterOpacity = 0.447;
|
||||||
|
|
||||||
/// An iOS-styled picker.
|
/// An iOS-styled picker.
|
||||||
///
|
///
|
||||||
@ -65,7 +69,7 @@ class CupertinoPicker extends StatefulWidget {
|
|||||||
CupertinoPicker({
|
CupertinoPicker({
|
||||||
Key key,
|
Key key,
|
||||||
this.diameterRatio = _kDefaultDiameterRatio,
|
this.diameterRatio = _kDefaultDiameterRatio,
|
||||||
this.backgroundColor = _kDefaultBackground,
|
this.backgroundColor,
|
||||||
this.offAxisFraction = 0.0,
|
this.offAxisFraction = 0.0,
|
||||||
this.useMagnifier = false,
|
this.useMagnifier = false,
|
||||||
this.magnification = 1.0,
|
this.magnification = 1.0,
|
||||||
@ -108,7 +112,7 @@ class CupertinoPicker extends StatefulWidget {
|
|||||||
CupertinoPicker.builder({
|
CupertinoPicker.builder({
|
||||||
Key key,
|
Key key,
|
||||||
this.diameterRatio = _kDefaultDiameterRatio,
|
this.diameterRatio = _kDefaultDiameterRatio,
|
||||||
this.backgroundColor = _kDefaultBackground,
|
this.backgroundColor,
|
||||||
this.offAxisFraction = 0.0,
|
this.offAxisFraction = 0.0,
|
||||||
this.useMagnifier = false,
|
this.useMagnifier = false,
|
||||||
this.magnification = 1.0,
|
this.magnification = 1.0,
|
||||||
@ -245,110 +249,32 @@ class _CupertinoPickerState extends State<CupertinoPicker> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes the fade to [CupertinoPicker.backgroundColor] edge gradients.
|
/// Draws the magnifier borders.
|
||||||
Widget _buildGradientScreen() {
|
|
||||||
// Because BlendMode.dstOut doesn't work correctly with BoxDecoration we
|
|
||||||
// have to just do a color blend. And a due to the way we are layering
|
|
||||||
// the magnifier and the gradient on the background, using a transparent
|
|
||||||
// background color makes the picker look odd.
|
|
||||||
if (widget.backgroundColor != null && widget.backgroundColor.alpha < 255)
|
|
||||||
return Container();
|
|
||||||
|
|
||||||
final Color widgetBackgroundColor = widget.backgroundColor ?? const Color(0xFFFFFFFF);
|
|
||||||
return Positioned.fill(
|
|
||||||
child: IgnorePointer(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: <Color>[
|
|
||||||
widgetBackgroundColor,
|
|
||||||
widgetBackgroundColor.withAlpha(0xF2),
|
|
||||||
widgetBackgroundColor.withAlpha(0xDD),
|
|
||||||
widgetBackgroundColor.withAlpha(0),
|
|
||||||
widgetBackgroundColor.withAlpha(0),
|
|
||||||
widgetBackgroundColor.withAlpha(0xDD),
|
|
||||||
widgetBackgroundColor.withAlpha(0xF2),
|
|
||||||
widgetBackgroundColor,
|
|
||||||
],
|
|
||||||
stops: const <double>[
|
|
||||||
0.0, 0.05, 0.09, 0.22, 0.78, 0.91, 0.95, 1.0,
|
|
||||||
],
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes the magnifier lens look so that the colors are normal through
|
|
||||||
/// the lens and partially grayed out around it.
|
|
||||||
Widget _buildMagnifierScreen() {
|
Widget _buildMagnifierScreen() {
|
||||||
final Color foreground = widget.backgroundColor?.withAlpha(
|
final Color resolvedBorderColor = CupertinoDynamicColor.resolve(_kHighlighterBorder, context);
|
||||||
(widget.backgroundColor.alpha * _kForegroundScreenOpacityFraction).toInt()
|
|
||||||
);
|
|
||||||
|
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
child: Column(
|
child: Center(
|
||||||
children: <Widget>[
|
child: Container(
|
||||||
Expanded(
|
decoration: BoxDecoration(
|
||||||
child: Container(
|
border: Border(
|
||||||
color: foreground,
|
top: BorderSide(width: 0.0, color: resolvedBorderColor),
|
||||||
|
bottom: BorderSide(width: 0.0, color: resolvedBorderColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(width: 0.0, color: _kHighlighterBorder),
|
|
||||||
bottom: BorderSide(width: 0.0, color: _kHighlighterBorder),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
constraints: BoxConstraints.expand(
|
|
||||||
height: widget.itemExtent * widget.magnification,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: foreground,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildUnderMagnifierScreen() {
|
|
||||||
final Color foreground = widget.backgroundColor?.withAlpha(
|
|
||||||
(widget.backgroundColor.alpha * _kForegroundScreenOpacityFraction).toInt()
|
|
||||||
);
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(child: Container()),
|
|
||||||
Container(
|
|
||||||
color: foreground,
|
|
||||||
constraints: BoxConstraints.expand(
|
constraints: BoxConstraints.expand(
|
||||||
height: widget.itemExtent * widget.magnification,
|
height: widget.itemExtent * widget.magnification,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: Container()),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _addBackgroundToChild(Widget child) {
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: widget.backgroundColor,
|
|
||||||
),
|
),
|
||||||
child: child,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget result = DefaultTextStyle(
|
final Color resolvedBackgroundColor = CupertinoDynamicColor.resolve(widget.backgroundColor, context);
|
||||||
|
|
||||||
|
final Widget result = DefaultTextStyle(
|
||||||
style: CupertinoTheme.of(context).textTheme.pickerTextStyle,
|
style: CupertinoTheme.of(context).textTheme.pickerTextStyle,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -363,6 +289,7 @@ class _CupertinoPickerState extends State<CupertinoPicker> {
|
|||||||
offAxisFraction: widget.offAxisFraction,
|
offAxisFraction: widget.offAxisFraction,
|
||||||
useMagnifier: widget.useMagnifier,
|
useMagnifier: widget.useMagnifier,
|
||||||
magnification: widget.magnification,
|
magnification: widget.magnification,
|
||||||
|
overAndUnderCenterOpacity: _kOverAndUnderCenterOpacity,
|
||||||
itemExtent: widget.itemExtent,
|
itemExtent: widget.itemExtent,
|
||||||
squeeze: widget.squeeze,
|
squeeze: widget.squeeze,
|
||||||
onSelectedItemChanged: _handleSelectedItemChanged,
|
onSelectedItemChanged: _handleSelectedItemChanged,
|
||||||
@ -370,24 +297,15 @@ class _CupertinoPickerState extends State<CupertinoPicker> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildGradientScreen(),
|
|
||||||
_buildMagnifierScreen(),
|
_buildMagnifierScreen(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// Adds the appropriate opacity under the magnifier if the background
|
|
||||||
// color is transparent.
|
return DecoratedBox(
|
||||||
if (widget.backgroundColor != null && widget.backgroundColor.alpha < 255) {
|
decoration: BoxDecoration(color: resolvedBackgroundColor),
|
||||||
result = Stack(
|
child: result,
|
||||||
children: <Widget> [
|
);
|
||||||
_buildUnderMagnifierScreen(),
|
|
||||||
_addBackgroundToChild(result),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
result = _addBackgroundToChild(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ class RenderListWheelViewport
|
|||||||
double offAxisFraction = 0,
|
double offAxisFraction = 0,
|
||||||
bool useMagnifier = false,
|
bool useMagnifier = false,
|
||||||
double magnification = 1,
|
double magnification = 1,
|
||||||
|
double overAndUnderCenterOpacity = 1,
|
||||||
@required double itemExtent,
|
@required double itemExtent,
|
||||||
double squeeze = 1,
|
double squeeze = 1,
|
||||||
bool clipToSize = true,
|
bool clipToSize = true,
|
||||||
@ -156,6 +157,8 @@ class RenderListWheelViewport
|
|||||||
assert(useMagnifier != null),
|
assert(useMagnifier != null),
|
||||||
assert(magnification != null),
|
assert(magnification != null),
|
||||||
assert(magnification > 0),
|
assert(magnification > 0),
|
||||||
|
assert(overAndUnderCenterOpacity != null),
|
||||||
|
assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
|
||||||
assert(itemExtent != null),
|
assert(itemExtent != null),
|
||||||
assert(squeeze != null),
|
assert(squeeze != null),
|
||||||
assert(squeeze > 0),
|
assert(squeeze > 0),
|
||||||
@ -172,6 +175,7 @@ class RenderListWheelViewport
|
|||||||
_offAxisFraction = offAxisFraction,
|
_offAxisFraction = offAxisFraction,
|
||||||
_useMagnifier = useMagnifier,
|
_useMagnifier = useMagnifier,
|
||||||
_magnification = magnification,
|
_magnification = magnification,
|
||||||
|
_overAndUnderCenterOpacity = overAndUnderCenterOpacity,
|
||||||
_itemExtent = itemExtent,
|
_itemExtent = itemExtent,
|
||||||
_squeeze = squeeze,
|
_squeeze = squeeze,
|
||||||
_clipToSize = clipToSize,
|
_clipToSize = clipToSize,
|
||||||
@ -368,6 +372,25 @@ class RenderListWheelViewport
|
|||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// {@template flutter.rendering.wheelList.overAndUnderCenterOpacity}
|
||||||
|
/// The opacity value that will be applied to the wheel that appears below and
|
||||||
|
/// above the magnifier.
|
||||||
|
///
|
||||||
|
/// The default value is 1.0, which will not change anything.
|
||||||
|
///
|
||||||
|
/// Must be greater than or equal to 0, and less than or equal to 1.
|
||||||
|
/// {@endtemplate}
|
||||||
|
double get overAndUnderCenterOpacity => _overAndUnderCenterOpacity;
|
||||||
|
double _overAndUnderCenterOpacity = 1.0;
|
||||||
|
set overAndUnderCenterOpacity(double value) {
|
||||||
|
assert(value != null);
|
||||||
|
assert(value >= 0 && value <= 1);
|
||||||
|
if (value == _overAndUnderCenterOpacity)
|
||||||
|
return;
|
||||||
|
_overAndUnderCenterOpacity = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
/// {@template flutter.rendering.wheelList.itemExtent}
|
/// {@template flutter.rendering.wheelList.itemExtent}
|
||||||
/// The size of the children along the main axis. Children [RenderBox]es will
|
/// The size of the children along the main axis. Children [RenderBox]es will
|
||||||
/// be given the [BoxConstraints] of this exact size.
|
/// be given the [BoxConstraints] of this exact size.
|
||||||
@ -822,20 +845,16 @@ class RenderListWheelViewport
|
|||||||
|
|
||||||
// Offset that helps painting everything in the center (e.g. angle = 0).
|
// Offset that helps painting everything in the center (e.g. angle = 0).
|
||||||
final Offset offsetToCenter = Offset(
|
final Offset offsetToCenter = Offset(
|
||||||
untransformedPaintingCoordinates.dx,
|
untransformedPaintingCoordinates.dx,
|
||||||
-_topScrollMarginExtent);
|
-_topScrollMarginExtent,
|
||||||
|
);
|
||||||
|
|
||||||
if (!useMagnifier)
|
final bool shouldApplyOffCenterDim = overAndUnderCenterOpacity < 1;
|
||||||
|
if (useMagnifier || shouldApplyOffCenterDim) {
|
||||||
|
_paintChildWithMagnifier(context, offset, child, transform, offsetToCenter, untransformedPaintingCoordinates);
|
||||||
|
} else {
|
||||||
_paintChildCylindrically(context, offset, child, transform, offsetToCenter);
|
_paintChildCylindrically(context, offset, child, transform, offsetToCenter);
|
||||||
else
|
}
|
||||||
_paintChildWithMagnifier(
|
|
||||||
context,
|
|
||||||
offset,
|
|
||||||
child,
|
|
||||||
transform,
|
|
||||||
offsetToCenter,
|
|
||||||
untransformedPaintingCoordinates,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paint child with the magnifier active - the child will be rendered
|
/// Paint child with the magnifier active - the child will be rendered
|
||||||
@ -878,36 +897,34 @@ class RenderListWheelViewport
|
|||||||
|
|
||||||
// Clipping the part in the center.
|
// Clipping the part in the center.
|
||||||
context.pushClipRect(
|
context.pushClipRect(
|
||||||
false,
|
needsCompositing,
|
||||||
offset,
|
offset,
|
||||||
centerRect,
|
centerRect,
|
||||||
(PaintingContext context, Offset offset) {
|
(PaintingContext context, Offset offset) {
|
||||||
context.pushTransform(
|
context.pushTransform(
|
||||||
false,
|
needsCompositing,
|
||||||
offset,
|
offset,
|
||||||
_magnifyTransform(),
|
_magnifyTransform(),
|
||||||
(PaintingContext context, Offset offset) {
|
(PaintingContext context, Offset offset) {
|
||||||
context.paintChild(
|
context.paintChild(child, offset + untransformedPaintingCoordinates);
|
||||||
child,
|
|
||||||
offset + untransformedPaintingCoordinates);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Clipping the part in either the top-half or bottom-half of the wheel.
|
// Clipping the part in either the top-half or bottom-half of the wheel.
|
||||||
context.pushClipRect(
|
context.pushClipRect(
|
||||||
false,
|
needsCompositing,
|
||||||
offset,
|
offset,
|
||||||
untransformedPaintingCoordinates.dy <= magnifierTopLinePosition
|
untransformedPaintingCoordinates.dy <= magnifierTopLinePosition
|
||||||
? topHalfRect
|
? topHalfRect
|
||||||
: bottomHalfRect,
|
: bottomHalfRect,
|
||||||
(PaintingContext context, Offset offset) {
|
(PaintingContext context, Offset offset) {
|
||||||
_paintChildCylindrically(
|
_paintChildCylindrically(
|
||||||
context,
|
context,
|
||||||
offset,
|
offset,
|
||||||
child,
|
child,
|
||||||
cylindricalTransform,
|
cylindricalTransform,
|
||||||
offsetToCenter);
|
offsetToCenter);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_paintChildCylindrically(
|
_paintChildCylindrically(
|
||||||
@ -927,20 +944,26 @@ class RenderListWheelViewport
|
|||||||
Matrix4 cylindricalTransform,
|
Matrix4 cylindricalTransform,
|
||||||
Offset offsetToCenter,
|
Offset offsetToCenter,
|
||||||
) {
|
) {
|
||||||
|
// Paint child cylindrically, without [overAndUnderCenterOpacity].
|
||||||
|
final PaintingContextCallback painter = (PaintingContext context, Offset offset) {
|
||||||
|
context.paintChild(
|
||||||
|
child,
|
||||||
|
// Paint everything in the center (e.g. angle = 0), then transform.
|
||||||
|
offset + offsetToCenter,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Paint child cylindrically, with [overAndUnderCenterOpacity].
|
||||||
|
final PaintingContextCallback opacityPainter = (PaintingContext context, Offset offset) {
|
||||||
|
context.pushOpacity(offset, (overAndUnderCenterOpacity * 255).round(), painter);
|
||||||
|
};
|
||||||
|
|
||||||
context.pushTransform(
|
context.pushTransform(
|
||||||
// Text with TransformLayers and no cullRects currently have an issue rendering
|
needsCompositing,
|
||||||
// https://github.com/flutter/flutter/issues/14224.
|
|
||||||
false,
|
|
||||||
offset,
|
offset,
|
||||||
_centerOriginTransform(cylindricalTransform),
|
_centerOriginTransform(cylindricalTransform),
|
||||||
// Pre-transform painting function.
|
// Pre-transform painting function.
|
||||||
(PaintingContext context, Offset offset) {
|
overAndUnderCenterOpacity == 1 ? painter : opacityPainter,
|
||||||
context.paintChild(
|
|
||||||
child,
|
|
||||||
// Paint everything in the center (e.g. angle = 0), then transform.
|
|
||||||
offset + offsetToCenter,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,6 +575,7 @@ class ListWheelScrollView extends StatefulWidget {
|
|||||||
this.offAxisFraction = 0.0,
|
this.offAxisFraction = 0.0,
|
||||||
this.useMagnifier = false,
|
this.useMagnifier = false,
|
||||||
this.magnification = 1.0,
|
this.magnification = 1.0,
|
||||||
|
this.overAndUnderCenterOpacity = 1.0,
|
||||||
@required this.itemExtent,
|
@required this.itemExtent,
|
||||||
this.squeeze = 1.0,
|
this.squeeze = 1.0,
|
||||||
this.onSelectedItemChanged,
|
this.onSelectedItemChanged,
|
||||||
@ -588,6 +589,8 @@ class ListWheelScrollView extends StatefulWidget {
|
|||||||
assert(perspective > 0),
|
assert(perspective > 0),
|
||||||
assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
|
assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
|
||||||
assert(magnification > 0),
|
assert(magnification > 0),
|
||||||
|
assert(overAndUnderCenterOpacity != null),
|
||||||
|
assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
|
||||||
assert(itemExtent != null),
|
assert(itemExtent != null),
|
||||||
assert(itemExtent > 0),
|
assert(itemExtent > 0),
|
||||||
assert(squeeze != null),
|
assert(squeeze != null),
|
||||||
@ -612,6 +615,7 @@ class ListWheelScrollView extends StatefulWidget {
|
|||||||
this.offAxisFraction = 0.0,
|
this.offAxisFraction = 0.0,
|
||||||
this.useMagnifier = false,
|
this.useMagnifier = false,
|
||||||
this.magnification = 1.0,
|
this.magnification = 1.0,
|
||||||
|
this.overAndUnderCenterOpacity = 1.0,
|
||||||
@required this.itemExtent,
|
@required this.itemExtent,
|
||||||
this.squeeze = 1.0,
|
this.squeeze = 1.0,
|
||||||
this.onSelectedItemChanged,
|
this.onSelectedItemChanged,
|
||||||
@ -625,6 +629,8 @@ class ListWheelScrollView extends StatefulWidget {
|
|||||||
assert(perspective > 0),
|
assert(perspective > 0),
|
||||||
assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
|
assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
|
||||||
assert(magnification > 0),
|
assert(magnification > 0),
|
||||||
|
assert(overAndUnderCenterOpacity != null),
|
||||||
|
assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
|
||||||
assert(itemExtent != null),
|
assert(itemExtent != null),
|
||||||
assert(itemExtent > 0),
|
assert(itemExtent > 0),
|
||||||
assert(squeeze != null),
|
assert(squeeze != null),
|
||||||
@ -677,6 +683,9 @@ class ListWheelScrollView extends StatefulWidget {
|
|||||||
/// {@macro flutter.rendering.wheelList.magnification}
|
/// {@macro flutter.rendering.wheelList.magnification}
|
||||||
final double magnification;
|
final double magnification;
|
||||||
|
|
||||||
|
/// {@macro flutter.rendering.wheelList.overAndUnderCenterOpacity}
|
||||||
|
final double overAndUnderCenterOpacity;
|
||||||
|
|
||||||
/// Size of each child in the main axis. Must not be null and must be
|
/// Size of each child in the main axis. Must not be null and must be
|
||||||
/// positive.
|
/// positive.
|
||||||
final double itemExtent;
|
final double itemExtent;
|
||||||
@ -757,6 +766,7 @@ class _ListWheelScrollViewState extends State<ListWheelScrollView> {
|
|||||||
offAxisFraction: widget.offAxisFraction,
|
offAxisFraction: widget.offAxisFraction,
|
||||||
useMagnifier: widget.useMagnifier,
|
useMagnifier: widget.useMagnifier,
|
||||||
magnification: widget.magnification,
|
magnification: widget.magnification,
|
||||||
|
overAndUnderCenterOpacity: widget.overAndUnderCenterOpacity,
|
||||||
itemExtent: widget.itemExtent,
|
itemExtent: widget.itemExtent,
|
||||||
squeeze: widget.squeeze,
|
squeeze: widget.squeeze,
|
||||||
clipToSize: widget.clipToSize,
|
clipToSize: widget.clipToSize,
|
||||||
@ -952,6 +962,7 @@ class ListWheelViewport extends RenderObjectWidget {
|
|||||||
this.offAxisFraction = 0.0,
|
this.offAxisFraction = 0.0,
|
||||||
this.useMagnifier = false,
|
this.useMagnifier = false,
|
||||||
this.magnification = 1.0,
|
this.magnification = 1.0,
|
||||||
|
this.overAndUnderCenterOpacity = 1.0,
|
||||||
@required this.itemExtent,
|
@required this.itemExtent,
|
||||||
this.squeeze = 1.0,
|
this.squeeze = 1.0,
|
||||||
this.clipToSize = true,
|
this.clipToSize = true,
|
||||||
@ -965,6 +976,8 @@ class ListWheelViewport extends RenderObjectWidget {
|
|||||||
assert(perspective != null),
|
assert(perspective != null),
|
||||||
assert(perspective > 0),
|
assert(perspective > 0),
|
||||||
assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
|
assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
|
||||||
|
assert(overAndUnderCenterOpacity != null),
|
||||||
|
assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
|
||||||
assert(itemExtent != null),
|
assert(itemExtent != null),
|
||||||
assert(itemExtent > 0),
|
assert(itemExtent > 0),
|
||||||
assert(squeeze != null),
|
assert(squeeze != null),
|
||||||
@ -992,6 +1005,9 @@ class ListWheelViewport extends RenderObjectWidget {
|
|||||||
/// {@macro flutter.rendering.wheelList.magnification}
|
/// {@macro flutter.rendering.wheelList.magnification}
|
||||||
final double magnification;
|
final double magnification;
|
||||||
|
|
||||||
|
/// {@macro flutter.rendering.wheelList.overAndUnderCenterOpacity}
|
||||||
|
final double overAndUnderCenterOpacity;
|
||||||
|
|
||||||
/// {@macro flutter.rendering.wheelList.itemExtent}
|
/// {@macro flutter.rendering.wheelList.itemExtent}
|
||||||
final double itemExtent;
|
final double itemExtent;
|
||||||
|
|
||||||
@ -1027,6 +1043,7 @@ class ListWheelViewport extends RenderObjectWidget {
|
|||||||
offAxisFraction: offAxisFraction,
|
offAxisFraction: offAxisFraction,
|
||||||
useMagnifier: useMagnifier,
|
useMagnifier: useMagnifier,
|
||||||
magnification: magnification,
|
magnification: magnification,
|
||||||
|
overAndUnderCenterOpacity: overAndUnderCenterOpacity,
|
||||||
itemExtent: itemExtent,
|
itemExtent: itemExtent,
|
||||||
squeeze: squeeze,
|
squeeze: squeeze,
|
||||||
clipToSize: clipToSize,
|
clipToSize: clipToSize,
|
||||||
@ -1043,6 +1060,7 @@ class ListWheelViewport extends RenderObjectWidget {
|
|||||||
..offAxisFraction = offAxisFraction
|
..offAxisFraction = offAxisFraction
|
||||||
..useMagnifier = useMagnifier
|
..useMagnifier = useMagnifier
|
||||||
..magnification = magnification
|
..magnification = magnification
|
||||||
|
..overAndUnderCenterOpacity = overAndUnderCenterOpacity
|
||||||
..itemExtent = itemExtent
|
..itemExtent = itemExtent
|
||||||
..squeeze = squeeze
|
..squeeze = squeeze
|
||||||
..clipToSize = clipToSize
|
..clipToSize = clipToSize
|
||||||
|
@ -115,19 +115,20 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
||||||
expect(pickers.any((CupertinoPicker picker) => picker.backgroundColor != CupertinoColors.white), false);
|
expect(pickers.any((CupertinoPicker picker) => picker.backgroundColor != null), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('background color is not null', (WidgetTester tester) async {
|
testWidgets('background color can be null', (WidgetTester tester) async {
|
||||||
expect(
|
await tester.pumpWidget(
|
||||||
() {
|
CupertinoApp(
|
||||||
CupertinoTimerPicker(
|
home: CupertinoTimerPicker(
|
||||||
onTimerDurationChanged: (_) { },
|
onTimerDurationChanged: (_) { },
|
||||||
backgroundColor: null,
|
backgroundColor: null,
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
throwsAssertionError,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('specified background color is applied', (WidgetTester tester) async {
|
testWidgets('specified background color is applied', (WidgetTester tester) async {
|
||||||
@ -316,19 +317,20 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
||||||
expect(pickers.any((CupertinoPicker picker) => picker.backgroundColor != CupertinoColors.white), false);
|
expect(pickers.any((CupertinoPicker picker) => picker.backgroundColor != null), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('background color is not null', (WidgetTester tester) async {
|
testWidgets('background color can be null', (WidgetTester tester) async {
|
||||||
expect(
|
await tester.pumpWidget(
|
||||||
() {
|
CupertinoApp(
|
||||||
CupertinoDatePicker(
|
home: CupertinoDatePicker(
|
||||||
onDateTimeChanged: (_) { },
|
onDateTimeChanged: (_) { },
|
||||||
backgroundColor: null,
|
backgroundColor: null,
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
throwsAssertionError,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('specified background color is applied', (WidgetTester tester) async {
|
testWidgets('specified background color is applied', (WidgetTester tester) async {
|
||||||
|
@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../rendering/mock_canvas.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Picker respects theme styling', (WidgetTester tester) async {
|
testWidgets('Picker respects theme styling', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -95,125 +97,56 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('gradient', () {
|
testWidgets('picker dark mode', (WidgetTester tester) async {
|
||||||
testWidgets('gradient displays correctly with background color', (WidgetTester tester) async {
|
await tester.pumpWidget(
|
||||||
const Color backgroundColor = Color.fromRGBO(255, 0, 0, 1.0);
|
CupertinoApp(
|
||||||
await tester.pumpWidget(
|
theme: const CupertinoThemeData(brightness: Brightness.light),
|
||||||
Directionality(
|
home: Align(
|
||||||
textDirection: TextDirection.ltr,
|
alignment: Alignment.topLeft,
|
||||||
child: Align(
|
child: SizedBox(
|
||||||
alignment: Alignment.topLeft,
|
height: 300.0,
|
||||||
child: SizedBox(
|
width: 300.0,
|
||||||
height: 300.0,
|
child: CupertinoPicker(
|
||||||
width: 300.0,
|
backgroundColor: const CupertinoDynamicColor.withBrightness(
|
||||||
child: CupertinoPicker(
|
color: Color(0xFF123456), // Set alpha channel to FF to disable under magnifier painting.
|
||||||
backgroundColor: backgroundColor,
|
darkColor: Color(0xFF654321),
|
||||||
itemExtent: 15.0,
|
|
||||||
children: const <Widget>[
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
],
|
|
||||||
onSelectedItemChanged: (int i) { },
|
|
||||||
),
|
),
|
||||||
|
itemExtent: 15.0,
|
||||||
|
children: const <Widget>[Text('1'), Text('1')],
|
||||||
|
onSelectedItemChanged: (int i) { },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
final Container container = tester.firstWidget(find.byType(Container));
|
);
|
||||||
final BoxDecoration boxDecoration = container.decoration as BoxDecoration;
|
|
||||||
expect(boxDecoration.gradient.colors, <Color>[
|
|
||||||
backgroundColor,
|
|
||||||
backgroundColor.withAlpha(0xF2),
|
|
||||||
backgroundColor.withAlpha(0xDD),
|
|
||||||
backgroundColor.withAlpha(0x00),
|
|
||||||
backgroundColor.withAlpha(0x00),
|
|
||||||
backgroundColor.withAlpha(0xDD),
|
|
||||||
backgroundColor.withAlpha(0xF2),
|
|
||||||
backgroundColor,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('No gradient displays with transparent background color', (WidgetTester tester) async {
|
expect(find.byType(CupertinoPicker), paints..path(color: const Color(0x33000000), style: PaintingStyle.stroke));
|
||||||
const Color backgroundColor = Color.fromRGBO(255, 0, 0, 0.5);
|
expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF123456)));
|
||||||
await tester.pumpWidget(
|
|
||||||
Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 300.0,
|
|
||||||
width: 300.0,
|
|
||||||
child: CupertinoPicker(
|
|
||||||
backgroundColor: backgroundColor,
|
|
||||||
itemExtent: 15.0,
|
|
||||||
children: const <Widget>[
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
],
|
|
||||||
onSelectedItemChanged: (int i) { },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final DecoratedBox decoratedBox = tester.firstWidget(find.byType(DecoratedBox));
|
|
||||||
final BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration;
|
|
||||||
expect(boxDecoration.gradient, isNull);
|
|
||||||
expect(boxDecoration.color, isNotNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('gradient displays correctly with null background color', (WidgetTester tester) async {
|
await tester.pumpWidget(
|
||||||
await tester.pumpWidget(
|
CupertinoApp(
|
||||||
Directionality(
|
theme: const CupertinoThemeData(brightness: Brightness.dark),
|
||||||
textDirection: TextDirection.ltr,
|
home: Align(
|
||||||
child: Align(
|
alignment: Alignment.topLeft,
|
||||||
alignment: Alignment.topLeft,
|
child: SizedBox(
|
||||||
child: SizedBox(
|
height: 300.0,
|
||||||
height: 300.0,
|
width: 300.0,
|
||||||
width: 300.0,
|
child: CupertinoPicker(
|
||||||
child: CupertinoPicker(
|
backgroundColor: const CupertinoDynamicColor.withBrightness(
|
||||||
backgroundColor: null,
|
color: Color(0xFF123456),
|
||||||
itemExtent: 15.0,
|
darkColor: Color(0xFF654321),
|
||||||
children: const <Widget>[
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
Text('1'),
|
|
||||||
],
|
|
||||||
onSelectedItemChanged: (int i) { },
|
|
||||||
),
|
),
|
||||||
|
itemExtent: 15.0,
|
||||||
|
children: const <Widget>[Text('1'), Text('1')],
|
||||||
|
onSelectedItemChanged: (int i) { },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
// If the background color is null, the gradient color should be white.
|
);
|
||||||
const Color backgroundColor = Color(0xFFFFFFFF);
|
|
||||||
final Container container = tester.firstWidget(find.byType(Container));
|
expect(find.byType(CupertinoPicker), paints..path(color: const Color(0x33FFFFFF), style: PaintingStyle.stroke));
|
||||||
final BoxDecoration boxDecoration = container.decoration as BoxDecoration;
|
expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF654321)));
|
||||||
expect(boxDecoration.gradient.colors, <Color>[
|
|
||||||
backgroundColor,
|
|
||||||
backgroundColor.withAlpha(0xF2),
|
|
||||||
backgroundColor.withAlpha(0xDD),
|
|
||||||
backgroundColor.withAlpha(0x00),
|
|
||||||
backgroundColor.withAlpha(0x00),
|
|
||||||
backgroundColor.withAlpha(0xDD),
|
|
||||||
backgroundColor.withAlpha(0xF2),
|
|
||||||
backgroundColor,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('scroll', () {
|
group('scroll', () {
|
||||||
|
@ -64,6 +64,63 @@ void main() {
|
|||||||
throwsAssertionError,
|
throwsAssertionError,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ListWheelScrollView needs valid overAndUnderCenterOpacity', (WidgetTester tester) async {
|
||||||
|
expect(
|
||||||
|
() {
|
||||||
|
ListWheelScrollView(
|
||||||
|
overAndUnderCenterOpacity: null,
|
||||||
|
itemExtent: 20.0,
|
||||||
|
children: <Widget>[Container()],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() {
|
||||||
|
ListWheelScrollView(
|
||||||
|
overAndUnderCenterOpacity: -1,
|
||||||
|
itemExtent: 20.0,
|
||||||
|
children: <Widget>[Container()],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() {
|
||||||
|
ListWheelScrollView(
|
||||||
|
overAndUnderCenterOpacity: 2,
|
||||||
|
itemExtent: 20.0,
|
||||||
|
children: <Widget>[Container()],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
throwsAssertionError,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() {
|
||||||
|
ListWheelScrollView(
|
||||||
|
overAndUnderCenterOpacity: 1,
|
||||||
|
itemExtent: 20.0,
|
||||||
|
children: <Widget>[Container()],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isNot(throwsAssertionError),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() {
|
||||||
|
ListWheelScrollView(
|
||||||
|
overAndUnderCenterOpacity: 0,
|
||||||
|
itemExtent: 20.0,
|
||||||
|
children: <Widget>[Container()],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isNot(throwsAssertionError),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('infinite scrolling', () {
|
group('infinite scrolling', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user