mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
SafeArea (#12292)
* SafeArea * AnimatedSafeArea * AppBar test * Apply feedback
This commit is contained in:
parent
9646e1fbad
commit
4c83ea8bef
|
@ -337,6 +337,9 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe
|
||||||
left: _kNavBarEdgePadding,
|
left: _kNavBarEdgePadding,
|
||||||
right: _kNavBarEdgePadding,
|
right: _kNavBarEdgePadding,
|
||||||
),
|
),
|
||||||
|
child: new MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
child: new NavigationToolbar(
|
child: new NavigationToolbar(
|
||||||
leading: styledLeading,
|
leading: styledLeading,
|
||||||
middle: animatedStyledMiddle,
|
middle: animatedStyledMiddle,
|
||||||
|
@ -345,6 +348,7 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,10 +54,9 @@ class CupertinoPageScaffold extends StatelessWidget {
|
||||||
if (topPadding > 0.0) {
|
if (topPadding > 0.0) {
|
||||||
final EdgeInsets mediaQueryPadding = MediaQuery.of(context).padding;
|
final EdgeInsets mediaQueryPadding = MediaQuery.of(context).padding;
|
||||||
topPadding += mediaQueryPadding.top;
|
topPadding += mediaQueryPadding.top;
|
||||||
childWithMediaQuery = new MediaQuery(
|
childWithMediaQuery = new MediaQuery.removePadding(
|
||||||
data: MediaQuery.of(context).copyWith(
|
context: context,
|
||||||
padding: mediaQueryPadding.copyWith(top: 0.0),
|
removeTop: true,
|
||||||
),
|
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -441,8 +441,8 @@ class _AppBarState extends State<AppBar> {
|
||||||
|
|
||||||
// The padding applies to the toolbar and tabbar, not the flexible space.
|
// The padding applies to the toolbar and tabbar, not the flexible space.
|
||||||
if (widget.primary) {
|
if (widget.primary) {
|
||||||
appBar = new Padding(
|
appBar = new SafeArea(
|
||||||
padding: new EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
top: true,
|
||||||
child: appBar,
|
child: appBar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,9 +93,13 @@ class DrawerHeader extends StatelessWidget {
|
||||||
curve: curve,
|
curve: curve,
|
||||||
child: child == null ? null : new DefaultTextStyle(
|
child: child == null ? null : new DefaultTextStyle(
|
||||||
style: theme.textTheme.body2,
|
style: theme.textTheme.body2,
|
||||||
|
child: new MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,15 @@ enum _ScaffoldSlot {
|
||||||
|
|
||||||
class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
||||||
_ScaffoldLayout({
|
_ScaffoldLayout({
|
||||||
@required this.padding,
|
|
||||||
@required this.statusBarHeight,
|
@required this.statusBarHeight,
|
||||||
|
@required this.bottomPadding,
|
||||||
|
@required this.endPadding, // for floating action button
|
||||||
@required this.textDirection,
|
@required this.textDirection,
|
||||||
});
|
});
|
||||||
|
|
||||||
final EdgeInsets padding;
|
|
||||||
final double statusBarHeight;
|
final double statusBarHeight;
|
||||||
|
final double bottomPadding;
|
||||||
|
final double endPadding;
|
||||||
final TextDirection textDirection;
|
final TextDirection textDirection;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -56,7 +58,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
||||||
// so the app bar's shadow is drawn on top of the body.
|
// so the app bar's shadow is drawn on top of the body.
|
||||||
|
|
||||||
final BoxConstraints fullWidthConstraints = looseConstraints.tighten(width: size.width);
|
final BoxConstraints fullWidthConstraints = looseConstraints.tighten(width: size.width);
|
||||||
final double bottom = math.max(0.0, size.height - padding.bottom);
|
final double bottom = math.max(0.0, size.height - bottomPadding);
|
||||||
double contentTop = 0.0;
|
double contentTop = 0.0;
|
||||||
double contentBottom = bottom;
|
double contentBottom = bottom;
|
||||||
|
|
||||||
|
@ -72,7 +74,11 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChild(_ScaffoldSlot.persistentFooter)) {
|
if (hasChild(_ScaffoldSlot.persistentFooter)) {
|
||||||
final double persistentFooterHeight = layoutChild(_ScaffoldSlot.persistentFooter, fullWidthConstraints.copyWith(maxHeight: contentBottom - contentTop)).height;
|
final BoxConstraints footerConstraints = new BoxConstraints(
|
||||||
|
maxWidth: fullWidthConstraints.maxWidth,
|
||||||
|
maxHeight: math.max(0.0, contentBottom - contentTop),
|
||||||
|
);
|
||||||
|
final double persistentFooterHeight = layoutChild(_ScaffoldSlot.persistentFooter, footerConstraints).height;
|
||||||
contentBottom -= persistentFooterHeight;
|
contentBottom -= persistentFooterHeight;
|
||||||
positionChild(_ScaffoldSlot.persistentFooter, new Offset(0.0, contentBottom));
|
positionChild(_ScaffoldSlot.persistentFooter, new Offset(0.0, contentBottom));
|
||||||
}
|
}
|
||||||
|
@ -102,7 +108,11 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
||||||
Size snackBarSize = Size.zero;
|
Size snackBarSize = Size.zero;
|
||||||
|
|
||||||
if (hasChild(_ScaffoldSlot.bottomSheet)) {
|
if (hasChild(_ScaffoldSlot.bottomSheet)) {
|
||||||
bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, fullWidthConstraints.copyWith(maxHeight: contentBottom - contentTop));
|
final BoxConstraints bottomSheetConstraints = new BoxConstraints(
|
||||||
|
maxWidth: fullWidthConstraints.maxWidth,
|
||||||
|
maxHeight: math.max(0.0, contentBottom - contentTop),
|
||||||
|
);
|
||||||
|
bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, bottomSheetConstraints);
|
||||||
positionChild(_ScaffoldSlot.bottomSheet, new Offset((size.width - bottomSheetSize.width) / 2.0, bottom - bottomSheetSize.height));
|
positionChild(_ScaffoldSlot.bottomSheet, new Offset((size.width - bottomSheetSize.width) / 2.0, bottom - bottomSheetSize.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,10 +127,10 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
||||||
assert(textDirection != null);
|
assert(textDirection != null);
|
||||||
switch (textDirection) {
|
switch (textDirection) {
|
||||||
case TextDirection.rtl:
|
case TextDirection.rtl:
|
||||||
fabX = _kFloatingActionButtonMargin;
|
fabX = _kFloatingActionButtonMargin + endPadding;
|
||||||
break;
|
break;
|
||||||
case TextDirection.ltr:
|
case TextDirection.ltr:
|
||||||
fabX = size.width - fabSize.width - _kFloatingActionButtonMargin;
|
fabX = size.width - fabSize.width - _kFloatingActionButtonMargin - endPadding;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
double fabY = contentBottom - fabSize.height - _kFloatingActionButtonMargin;
|
double fabY = contentBottom - fabSize.height - _kFloatingActionButtonMargin;
|
||||||
|
@ -144,8 +154,9 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRelayout(_ScaffoldLayout oldDelegate) {
|
bool shouldRelayout(_ScaffoldLayout oldDelegate) {
|
||||||
return oldDelegate.padding != padding
|
return oldDelegate.statusBarHeight != statusBarHeight
|
||||||
|| oldDelegate.statusBarHeight != statusBarHeight
|
|| oldDelegate.bottomPadding != bottomPadding
|
||||||
|
|| oldDelegate.endPadding != endPadding
|
||||||
|| oldDelegate.textDirection != textDirection;
|
|| oldDelegate.textDirection != textDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,10 +347,13 @@ class Scaffold extends StatefulWidget {
|
||||||
/// A set of buttons that are displayed at the bottom of the scaffold.
|
/// A set of buttons that are displayed at the bottom of the scaffold.
|
||||||
///
|
///
|
||||||
/// Typically this is a list of [FlatButton] widgets. These buttons are
|
/// Typically this is a list of [FlatButton] widgets. These buttons are
|
||||||
/// persistently visible, even of the [body] of the scaffold scrolls.
|
/// persistently visible, even if the [body] of the scaffold scrolls.
|
||||||
///
|
///
|
||||||
/// These widgets will be wrapped in a [ButtonBar].
|
/// These widgets will be wrapped in a [ButtonBar].
|
||||||
///
|
///
|
||||||
|
/// The [persistentFooterButtons] are rendered above the
|
||||||
|
/// [bottomNavigationBar] but below the [body].
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * <https://material.google.com/components/buttons.html#buttons-persistent-footer-buttons>
|
/// * <https://material.google.com/components/buttons.html#buttons-persistent-footer-buttons>
|
||||||
|
@ -363,6 +377,9 @@ class Scaffold extends StatefulWidget {
|
||||||
///
|
///
|
||||||
/// Snack bars slide from underneath the bottom navigation bar while bottom
|
/// Snack bars slide from underneath the bottom navigation bar while bottom
|
||||||
/// sheets are stacked on top.
|
/// sheets are stacked on top.
|
||||||
|
///
|
||||||
|
/// The [bottomNavigationBar] is rendered below the [persistentFooterButtons]
|
||||||
|
/// and the [body].
|
||||||
final Widget bottomNavigationBar;
|
final Widget bottomNavigationBar;
|
||||||
|
|
||||||
/// Whether the [body] (and other floating widgets) should size themselves to
|
/// Whether the [body] (and other floating widgets) should size themselves to
|
||||||
|
@ -747,18 +764,36 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addIfNonNull(List<LayoutId> children, Widget child, Object childId) {
|
void _addIfNonNull(List<LayoutId> children, Widget child, Object childId, {
|
||||||
if (child != null)
|
@required bool removeLeftPadding,
|
||||||
children.add(new LayoutId(child: child, id: childId));
|
@required bool removeTopPadding,
|
||||||
|
@required bool removeRightPadding,
|
||||||
|
bool removeBottomPadding, // defaults to widget.resizeToAvoidBottomPadding
|
||||||
|
}) {
|
||||||
|
if (child != null) {
|
||||||
|
children.add(
|
||||||
|
new LayoutId(
|
||||||
|
id: childId,
|
||||||
|
child: new MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeLeft: removeLeftPadding,
|
||||||
|
removeTop: removeTopPadding,
|
||||||
|
removeRight: removeRightPadding,
|
||||||
|
removeBottom: removeBottomPadding ?? widget.resizeToAvoidBottomPadding,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(debugCheckHasMediaQuery(context));
|
assert(debugCheckHasMediaQuery(context));
|
||||||
EdgeInsets padding = MediaQuery.of(context).padding;
|
assert(debugCheckHasDirectionality(context));
|
||||||
|
final EdgeInsets padding = MediaQuery.of(context).padding;
|
||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
if (!widget.resizeToAvoidBottomPadding)
|
final TextDirection textDirection = Directionality.of(context);
|
||||||
padding = new EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, 0.0);
|
|
||||||
|
|
||||||
if (_snackBars.isNotEmpty) {
|
if (_snackBars.isNotEmpty) {
|
||||||
final ModalRoute<dynamic> route = ModalRoute.of(context);
|
final ModalRoute<dynamic> route = ModalRoute.of(context);
|
||||||
|
@ -777,7 +812,14 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
|
||||||
|
|
||||||
final List<LayoutId> children = <LayoutId>[];
|
final List<LayoutId> children = <LayoutId>[];
|
||||||
|
|
||||||
_addIfNonNull(children, widget.body, _ScaffoldSlot.body);
|
_addIfNonNull(
|
||||||
|
children,
|
||||||
|
widget.body,
|
||||||
|
_ScaffoldSlot.body,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: widget.appBar != null,
|
||||||
|
removeRightPadding: false,
|
||||||
|
);
|
||||||
|
|
||||||
if (widget.appBar != null) {
|
if (widget.appBar != null) {
|
||||||
final double topPadding = widget.primary ? padding.top : 0.0;
|
final double topPadding = widget.primary ? padding.top : 0.0;
|
||||||
|
@ -793,16 +835,28 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_ScaffoldSlot.appBar,
|
_ScaffoldSlot.appBar,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: false,
|
||||||
|
removeRightPadding: false,
|
||||||
|
removeBottomPadding: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_snackBars.isNotEmpty)
|
if (_snackBars.isNotEmpty) {
|
||||||
_addIfNonNull(children, _snackBars.first._widget, _ScaffoldSlot.snackBar);
|
_addIfNonNull(
|
||||||
|
children,
|
||||||
|
_snackBars.first._widget,
|
||||||
|
_ScaffoldSlot.snackBar,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: true,
|
||||||
|
removeRightPadding: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.persistentFooterButtons != null) {
|
if (widget.persistentFooterButtons != null) {
|
||||||
children.add(new LayoutId(
|
_addIfNonNull(
|
||||||
id: _ScaffoldSlot.persistentFooter,
|
children,
|
||||||
child: new Container(
|
new Container(
|
||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
border: new Border(
|
border: new Border(
|
||||||
top: new BorderSide(
|
top: new BorderSide(
|
||||||
|
@ -810,20 +864,30 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: new SafeArea(
|
||||||
child: new ButtonTheme.bar(
|
child: new ButtonTheme.bar(
|
||||||
child: new ButtonBar(
|
child: new ButtonBar(
|
||||||
children: widget.persistentFooterButtons
|
children: widget.persistentFooterButtons
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
_ScaffoldSlot.persistentFooter,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: true,
|
||||||
|
removeRightPadding: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.bottomNavigationBar != null) {
|
if (widget.bottomNavigationBar != null) {
|
||||||
children.add(new LayoutId(
|
_addIfNonNull(
|
||||||
id: _ScaffoldSlot.bottomNavigationBar,
|
children,
|
||||||
child: widget.bottomNavigationBar,
|
widget.bottomNavigationBar,
|
||||||
));
|
_ScaffoldSlot.bottomNavigationBar,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: true,
|
||||||
|
removeRightPadding: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
|
if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
|
||||||
|
@ -836,39 +900,73 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
|
||||||
children: bottomSheets,
|
children: bottomSheets,
|
||||||
alignment: FractionalOffset.bottomCenter,
|
alignment: FractionalOffset.bottomCenter,
|
||||||
);
|
);
|
||||||
_addIfNonNull(children, stack, _ScaffoldSlot.bottomSheet);
|
_addIfNonNull(
|
||||||
|
children,
|
||||||
|
stack,
|
||||||
|
_ScaffoldSlot.bottomSheet,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: true,
|
||||||
|
removeRightPadding: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
children.add(new LayoutId(
|
_addIfNonNull(
|
||||||
id: _ScaffoldSlot.floatingActionButton,
|
children,
|
||||||
child: new _FloatingActionButtonTransition(
|
new _FloatingActionButtonTransition(
|
||||||
child: widget.floatingActionButton,
|
child: widget.floatingActionButton,
|
||||||
)
|
),
|
||||||
));
|
_ScaffoldSlot.floatingActionButton,
|
||||||
|
removeLeftPadding: true,
|
||||||
|
removeTopPadding: true,
|
||||||
|
removeRightPadding: true,
|
||||||
|
removeBottomPadding: true,
|
||||||
|
);
|
||||||
|
|
||||||
if (themeData.platform == TargetPlatform.iOS) {
|
if (themeData.platform == TargetPlatform.iOS) {
|
||||||
children.add(new LayoutId(
|
_addIfNonNull(
|
||||||
id: _ScaffoldSlot.statusBar,
|
children,
|
||||||
child: new GestureDetector(
|
new GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: _handleStatusBarTap,
|
onTap: _handleStatusBarTap,
|
||||||
// iOS accessibility automatically adds scroll-to-top to the clock in the status bar
|
// iOS accessibility automatically adds scroll-to-top to the clock in the status bar
|
||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
)
|
),
|
||||||
));
|
_ScaffoldSlot.statusBar,
|
||||||
|
removeLeftPadding: false,
|
||||||
|
removeTopPadding: true,
|
||||||
|
removeRightPadding: false,
|
||||||
|
removeBottomPadding: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.drawer != null) {
|
if (widget.drawer != null) {
|
||||||
assert(hasDrawer);
|
assert(hasDrawer);
|
||||||
children.add(new LayoutId(
|
_addIfNonNull(
|
||||||
id: _ScaffoldSlot.drawer,
|
children,
|
||||||
child: new DrawerController(
|
new DrawerController(
|
||||||
key: _drawerKey,
|
key: _drawerKey,
|
||||||
child: widget.drawer,
|
child: widget.drawer,
|
||||||
)
|
),
|
||||||
));
|
_ScaffoldSlot.drawer,
|
||||||
|
// remove the side padding from the side we're not touching
|
||||||
|
removeLeftPadding: textDirection == TextDirection.rtl,
|
||||||
|
removeTopPadding: false,
|
||||||
|
removeRightPadding: textDirection == TextDirection.ltr,
|
||||||
|
removeBottomPadding: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double endPadding;
|
||||||
|
switch (textDirection) {
|
||||||
|
case TextDirection.rtl:
|
||||||
|
endPadding = padding.left;
|
||||||
|
break;
|
||||||
|
case TextDirection.ltr:
|
||||||
|
endPadding = padding.right;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
assert(endPadding != null);
|
||||||
|
|
||||||
return new _ScaffoldScope(
|
return new _ScaffoldScope(
|
||||||
hasDrawer: hasDrawer,
|
hasDrawer: hasDrawer,
|
||||||
child: new PrimaryScrollController(
|
child: new PrimaryScrollController(
|
||||||
|
@ -878,9 +976,10 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
|
||||||
child: new CustomMultiChildLayout(
|
child: new CustomMultiChildLayout(
|
||||||
children: children,
|
children: children,
|
||||||
delegate: new _ScaffoldLayout(
|
delegate: new _ScaffoldLayout(
|
||||||
padding: padding,
|
|
||||||
statusBarHeight: padding.top,
|
statusBarHeight: padding.top,
|
||||||
textDirection: Directionality.of(context),
|
bottomPadding: widget.resizeToAvoidBottomPadding ? padding.bottom : 0.0,
|
||||||
|
endPadding: endPadding,
|
||||||
|
textDirection: textDirection,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -336,6 +336,11 @@ abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> exten
|
||||||
/// For more complex animations, you'll likely want to use a subclass of
|
/// For more complex animations, you'll likely want to use a subclass of
|
||||||
/// [AnimatedWidget] such as the [DecoratedBoxTransition] or use your own
|
/// [AnimatedWidget] such as the [DecoratedBoxTransition] or use your own
|
||||||
/// [AnimationController].
|
/// [AnimationController].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [AnimatedPadding], which is a subset of this widget that only
|
||||||
|
/// supports animating the [padding].
|
||||||
class AnimatedContainer extends ImplicitlyAnimatedWidget {
|
class AnimatedContainer extends ImplicitlyAnimatedWidget {
|
||||||
/// Creates a container that animates its parameters implicitly.
|
/// Creates a container that animates its parameters implicitly.
|
||||||
///
|
///
|
||||||
|
@ -479,6 +484,66 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Animated version of [Padding] which automatically transitions the
|
||||||
|
/// indentation over a given duration whenever the given inset changes.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [AnimatedContainer], which can transition more values at once.
|
||||||
|
class AnimatedPadding extends ImplicitlyAnimatedWidget {
|
||||||
|
/// Creates a widget that insets its child by a value that animates
|
||||||
|
/// implicitly.
|
||||||
|
///
|
||||||
|
/// The [padding], [curve], and [duration] arguments must not be null.
|
||||||
|
AnimatedPadding({
|
||||||
|
Key key,
|
||||||
|
@required this.padding,
|
||||||
|
this.child,
|
||||||
|
Curve curve: Curves.linear,
|
||||||
|
@required Duration duration,
|
||||||
|
}) : assert(padding != null),
|
||||||
|
assert(padding.isNonNegative),
|
||||||
|
super(key: key, curve: curve, duration: duration);
|
||||||
|
|
||||||
|
/// The amount of space by which to inset the child.
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
|
/// The widget below this widget in the tree.
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AnimatedPaddingState createState() => new _AnimatedPaddingState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
||||||
|
super.debugFillProperties(description);
|
||||||
|
description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> {
|
||||||
|
EdgeInsetsGeometryTween _padding;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||||
|
_padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsGeometryTween(begin: value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Padding(
|
||||||
|
padding: _padding.evaluate(animation),
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
||||||
|
super.debugFillProperties(description);
|
||||||
|
description.add(new DiagnosticsProperty<EdgeInsetsGeometryTween>('padding', _padding, defaultValue: null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Animated version of [Positioned] which automatically transitions the child's
|
/// Animated version of [Positioned] which automatically transitions the child's
|
||||||
/// position over a given duration whenever the given position changes.
|
/// position over a given duration whenever the given position changes.
|
||||||
///
|
///
|
||||||
|
|
|
@ -98,6 +98,40 @@ class MediaQueryData {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a copy of this media query data but with the given paddings
|
||||||
|
/// replaced with zero.
|
||||||
|
///
|
||||||
|
/// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
|
||||||
|
/// must not be null. If all four are false (the default) then this
|
||||||
|
/// [MediaQueryData] is returned unmodified.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [new MediaQuery.removePadding], which uses this method to remove padding
|
||||||
|
/// from the ambient [MediaQuery].
|
||||||
|
/// * [SafeArea], which both removes the padding from the [MediaQuery] and
|
||||||
|
/// adds a [Padding] widget.
|
||||||
|
MediaQueryData removePadding({
|
||||||
|
bool removeLeft: false,
|
||||||
|
bool removeTop: false,
|
||||||
|
bool removeRight: false,
|
||||||
|
bool removeBottom: false,
|
||||||
|
}) {
|
||||||
|
if (!(removeLeft || removeTop || removeRight || removeBottom))
|
||||||
|
return this;
|
||||||
|
return new MediaQueryData(
|
||||||
|
size: size,
|
||||||
|
devicePixelRatio: devicePixelRatio,
|
||||||
|
textScaleFactor: textScaleFactor,
|
||||||
|
padding: padding.copyWith(
|
||||||
|
left: removeLeft ? 0.0 : null,
|
||||||
|
top: removeTop ? 0.0 : null,
|
||||||
|
right: removeRight ? 0.0 : null,
|
||||||
|
bottom: removeBottom ? 0.0 : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other.runtimeType != runtimeType)
|
if (other.runtimeType != runtimeType)
|
||||||
|
@ -148,6 +182,44 @@ class MediaQuery extends InheritedWidget {
|
||||||
assert(data != null),
|
assert(data != null),
|
||||||
super(key: key, child: child);
|
super(key: key, child: child);
|
||||||
|
|
||||||
|
/// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery] from
|
||||||
|
/// the given context, but removes the specified paddings.
|
||||||
|
///
|
||||||
|
/// The [context] argument is required, must not be null, and must have a
|
||||||
|
/// [MediaQuery] in scope.
|
||||||
|
///
|
||||||
|
/// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
|
||||||
|
/// must not be null. If all four are false (the default) then the returned
|
||||||
|
/// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
|
||||||
|
/// particularly useful.
|
||||||
|
///
|
||||||
|
/// The [child] argument is required and must not be null.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [SafeArea], which both removes the padding from the [MediaQuery] and
|
||||||
|
/// adds a [Padding] widget.
|
||||||
|
factory MediaQuery.removePadding({
|
||||||
|
Key key,
|
||||||
|
@required BuildContext context,
|
||||||
|
bool removeLeft: false,
|
||||||
|
bool removeTop: false,
|
||||||
|
bool removeRight: false,
|
||||||
|
bool removeBottom: false,
|
||||||
|
@required Widget child,
|
||||||
|
}) {
|
||||||
|
return new MediaQuery(
|
||||||
|
key: key,
|
||||||
|
data: MediaQuery.of(context).removePadding(
|
||||||
|
removeLeft: removeLeft,
|
||||||
|
removeTop: removeTop,
|
||||||
|
removeRight: removeRight,
|
||||||
|
removeBottom: removeBottom,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains information about the current media.
|
/// Contains information about the current media.
|
||||||
///
|
///
|
||||||
/// For example, the [MediaQueryData.size] property contains the width and
|
/// For example, the [MediaQueryData.size] property contains the width and
|
||||||
|
|
93
packages/flutter/lib/src/widgets/safe_area.dart
Normal file
93
packages/flutter/lib/src/widgets/safe_area.dart
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2017 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:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'basic.dart';
|
||||||
|
import 'debug.dart';
|
||||||
|
import 'framework.dart';
|
||||||
|
import 'media_query.dart';
|
||||||
|
|
||||||
|
/// A widget that insets its child by sufficient padding to avoid
|
||||||
|
/// intrusions by the operating system.
|
||||||
|
///
|
||||||
|
/// For example, this will indent the child by enough to avoid the status bar at
|
||||||
|
/// the top of the screen.
|
||||||
|
///
|
||||||
|
/// It will also indent the child by the amount necessary to avoid The Notch on
|
||||||
|
/// the iPhone X, or other similar creative physical features of the display.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [Padding], for insetting widgets in general.
|
||||||
|
/// * [MediaQuery], from which the window padding is obtained.
|
||||||
|
/// * [dart:ui.Window.padding], which reports the padding from the operating
|
||||||
|
/// system.
|
||||||
|
class SafeArea extends StatelessWidget {
|
||||||
|
/// Creates a widget that avoids operating system interfaces.
|
||||||
|
///
|
||||||
|
/// The [left], [top], [right], and [bottom] arguments must not be null.
|
||||||
|
const SafeArea({
|
||||||
|
Key key,
|
||||||
|
this.left: true,
|
||||||
|
this.top: true,
|
||||||
|
this.right: true,
|
||||||
|
this.bottom: true,
|
||||||
|
@required this.child,
|
||||||
|
}) : assert(left != null),
|
||||||
|
assert(top != null),
|
||||||
|
assert(right != null),
|
||||||
|
assert(bottom != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// Whether to avoid system intrusions on the left.
|
||||||
|
final bool left;
|
||||||
|
|
||||||
|
/// Whether to avoid system intrusions at the top of the screen, typically the
|
||||||
|
/// system status bar.
|
||||||
|
final bool top;
|
||||||
|
|
||||||
|
/// Whether to avoid system intrusions on the right.
|
||||||
|
final bool right;
|
||||||
|
|
||||||
|
/// Whether to avoid system intrusions on the bottom side of the screen.
|
||||||
|
final bool bottom;
|
||||||
|
|
||||||
|
/// The widget below this widget in the tree.
|
||||||
|
///
|
||||||
|
/// The padding on the [MediaQuery] for the [child] will be suitably adjusted
|
||||||
|
/// to zero out any sides that were avoided by this widget.
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(debugCheckHasMediaQuery(context));
|
||||||
|
final EdgeInsets padding = MediaQuery.of(context).padding;
|
||||||
|
return new Padding(
|
||||||
|
padding: new EdgeInsets.only(
|
||||||
|
left: left ? padding.left : 0.0,
|
||||||
|
top: top ? padding.top : 0.0,
|
||||||
|
right: right ? padding.right : 0.0,
|
||||||
|
bottom: bottom ? padding.bottom : 0.0,
|
||||||
|
),
|
||||||
|
child: new MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeLeft: left,
|
||||||
|
removeTop: top,
|
||||||
|
removeRight: right,
|
||||||
|
removeBottom: bottom,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
||||||
|
super.debugFillProperties(description);
|
||||||
|
description.add(new FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
|
||||||
|
description.add(new FlagProperty('top', value: left, ifTrue: 'avoid top padding'));
|
||||||
|
description.add(new FlagProperty('right', value: left, ifTrue: 'avoid right padding'));
|
||||||
|
description.add(new FlagProperty('bottom', value: left, ifTrue: 'avoid bottom padding'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,7 @@ export 'src/widgets/preferred_size.dart';
|
||||||
export 'src/widgets/primary_scroll_controller.dart';
|
export 'src/widgets/primary_scroll_controller.dart';
|
||||||
export 'src/widgets/raw_keyboard_listener.dart';
|
export 'src/widgets/raw_keyboard_listener.dart';
|
||||||
export 'src/widgets/routes.dart';
|
export 'src/widgets/routes.dart';
|
||||||
|
export 'src/widgets/safe_area.dart';
|
||||||
export 'src/widgets/scroll_activity.dart';
|
export 'src/widgets/scroll_activity.dart';
|
||||||
export 'src/widgets/scroll_configuration.dart';
|
export 'src/widgets/scroll_configuration.dart';
|
||||||
export 'src/widgets/scroll_context.dart';
|
export 'src/widgets/scroll_context.dart';
|
||||||
|
|
|
@ -1085,4 +1085,64 @@ void main() {
|
||||||
expect(tester.renderObject<RenderBox>(find.byKey(key)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
|
expect(tester.renderObject<RenderBox>(find.byKey(key)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
|
||||||
expect(tester.renderObject<RenderBox>(find.byKey(key)).size, const Size(56.0, 56.0));
|
expect(tester.renderObject<RenderBox>(find.byKey(key)).size, const Size(56.0, 56.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('AppBar positioning of leading and trailing widgets with top padding', (WidgetTester tester) async {
|
||||||
|
const MediaQueryData topPadding100 = const MediaQueryData(padding: const EdgeInsets.only(top: 100.0));
|
||||||
|
|
||||||
|
final Key leadingKey = new UniqueKey();
|
||||||
|
final Key titleKey = new UniqueKey();
|
||||||
|
final Key trailingKey = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new MediaQuery(
|
||||||
|
data: topPadding100,
|
||||||
|
child: new Scaffold(
|
||||||
|
primary: false,
|
||||||
|
appBar: new AppBar(
|
||||||
|
leading: new Placeholder(key: leadingKey),
|
||||||
|
title: new Placeholder(key: titleKey),
|
||||||
|
actions: <Widget>[ new Placeholder(key: trailingKey) ],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(find.byType(AppBar)), const Offset(0.0, 0.0));
|
||||||
|
expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(800.0 - 56.0, 100.0));
|
||||||
|
expect(tester.getTopLeft(find.byKey(titleKey)), const Offset(420.0, 100.0));
|
||||||
|
expect(tester.getTopLeft(find.byKey(trailingKey)), const Offset(4.0, 100.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverAppBar positioning of leading and trailing widgets with top padding', (WidgetTester tester) async {
|
||||||
|
const MediaQueryData topPadding100 = const MediaQueryData(padding: const EdgeInsets.only(top: 100.0));
|
||||||
|
|
||||||
|
final Key leadingKey = new UniqueKey();
|
||||||
|
final Key titleKey = new UniqueKey();
|
||||||
|
final Key trailingKey = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new MediaQuery(
|
||||||
|
data: topPadding100,
|
||||||
|
child: new CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
new SliverAppBar(
|
||||||
|
leading: new Placeholder(key: leadingKey),
|
||||||
|
title: new Placeholder(key: titleKey),
|
||||||
|
actions: <Widget>[ new Placeholder(key: trailingKey) ],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(find.byType(AppBar)), const Offset(0.0, 0.0));
|
||||||
|
expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(800.0 - 56.0, 100.0));
|
||||||
|
expect(tester.getTopLeft(find.byKey(titleKey)), const Offset(420.0, 100.0));
|
||||||
|
expect(tester.getTopLeft(find.byKey(trailingKey)), const Offset(4.0, 100.0));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,4 +527,100 @@ void main() {
|
||||||
|
|
||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Scaffold and extreme window padding', (WidgetTester tester) async {
|
||||||
|
final Key appBar = new UniqueKey();
|
||||||
|
final Key body = new UniqueKey();
|
||||||
|
final Key floatingActionButton = new UniqueKey();
|
||||||
|
final Key persistentFooterButton = new UniqueKey();
|
||||||
|
final Key drawer = new UniqueKey();
|
||||||
|
final Key bottomNavigationBar = new UniqueKey();
|
||||||
|
final Key insideAppBar = new UniqueKey();
|
||||||
|
final Key insideBody = new UniqueKey();
|
||||||
|
final Key insideFloatingActionButton = new UniqueKey();
|
||||||
|
final Key insidePersistentFooterButton = new UniqueKey();
|
||||||
|
final Key insideDrawer = new UniqueKey();
|
||||||
|
final Key insideBottomNavigationBar = new UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new MediaQuery(
|
||||||
|
data: const MediaQueryData(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 20.0,
|
||||||
|
top: 30.0,
|
||||||
|
right: 50.0,
|
||||||
|
bottom: 70.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: new Scaffold(
|
||||||
|
appBar: new PreferredSize(
|
||||||
|
preferredSize: const Size(11.0, 13.0),
|
||||||
|
child: new Container(
|
||||||
|
key: appBar,
|
||||||
|
child: new SafeArea(
|
||||||
|
child: new Placeholder(key: insideAppBar),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: new Container(
|
||||||
|
key: body,
|
||||||
|
child: new SafeArea(
|
||||||
|
child: new Placeholder(key: insideBody),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton: new SizedBox(
|
||||||
|
key: floatingActionButton,
|
||||||
|
width: 77.0,
|
||||||
|
height: 77.0,
|
||||||
|
child: new SafeArea(
|
||||||
|
child: new Placeholder(key: insideFloatingActionButton),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
persistentFooterButtons: <Widget>[
|
||||||
|
new SizedBox(
|
||||||
|
key: persistentFooterButton,
|
||||||
|
width: 100.0,
|
||||||
|
height: 90.0,
|
||||||
|
child: new SafeArea(
|
||||||
|
child: new Placeholder(key: insidePersistentFooterButton),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
drawer: new Container(
|
||||||
|
key: drawer,
|
||||||
|
width: 204.0,
|
||||||
|
child: new SafeArea(
|
||||||
|
child: new Placeholder(key: insideDrawer),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: new SizedBox(
|
||||||
|
key: bottomNavigationBar,
|
||||||
|
height: 55.0,
|
||||||
|
child: new SafeArea(
|
||||||
|
child: new Placeholder(key: insideBottomNavigationBar),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// open drawer
|
||||||
|
await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(tester.getRect(find.byKey(appBar)), new Rect.fromLTRB(0.0, 0.0, 800.0, 43.0));
|
||||||
|
expect(tester.getRect(find.byKey(body)), new Rect.fromLTRB(0.0, 43.0, 800.0, 368.0));
|
||||||
|
expect(tester.getRect(find.byKey(floatingActionButton)), new Rect.fromLTRB(36.0, 275.0, 113.0, 352.0));
|
||||||
|
expect(tester.getRect(find.byKey(persistentFooterButton)), new Rect.fromLTRB(28.0, 377.0, 128.0, 467.0));
|
||||||
|
expect(tester.getRect(find.byKey(drawer)), new Rect.fromLTRB(596.0, 0.0, 800.0, 600.0));
|
||||||
|
expect(tester.getRect(find.byKey(bottomNavigationBar)), new Rect.fromLTRB(0.0, 475.0, 800.0, 530.0));
|
||||||
|
expect(tester.getRect(find.byKey(insideAppBar)), new Rect.fromLTRB(20.0, 30.0, 750.0, 43.0));
|
||||||
|
expect(tester.getRect(find.byKey(insideBody)), new Rect.fromLTRB(20.0, 43.0, 750.0, 368.0));
|
||||||
|
expect(tester.getRect(find.byKey(insideFloatingActionButton)), new Rect.fromLTRB(36.0, 275.0, 113.0, 352.0));
|
||||||
|
expect(tester.getRect(find.byKey(insidePersistentFooterButton)), new Rect.fromLTRB(28.0, 377.0, 128.0, 467.0));
|
||||||
|
expect(tester.getRect(find.byKey(insideDrawer)), new Rect.fromLTRB(596.0, 30.0, 750.0, 530.0));
|
||||||
|
expect(tester.getRect(find.byKey(insideBottomNavigationBar)), new Rect.fromLTRB(20.0, 475.0, 750.0, 530.0));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
61
packages/flutter/test/widgets/animated_padding_test.dart
Normal file
61
packages/flutter/test/widgets/animated_padding_test.dart
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// 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:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('AnimatedPadding.debugFillProperties', (WidgetTester tester) async {
|
||||||
|
final AnimatedPadding padding = new AnimatedPadding(
|
||||||
|
padding: const EdgeInsets.all(7.0),
|
||||||
|
curve: Curves.ease,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(padding, hasOneLineDescription);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('AnimatedPadding padding visual-to-directional animation', (WidgetTester tester) async {
|
||||||
|
final Key target = new UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new AnimatedPadding(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
padding: const EdgeInsets.only(right: 50.0),
|
||||||
|
child: new SizedBox.expand(key: target),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.getSize(find.byKey(target)), const Size(750.0, 600.0));
|
||||||
|
expect(tester.getTopRight(find.byKey(target)), const Offset(750.0, 0.0));
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: new AnimatedPadding(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 100.0),
|
||||||
|
child: new SizedBox.expand(key: target),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(tester.getSize(find.byKey(target)), const Size(750.0, 600.0));
|
||||||
|
expect(tester.getTopRight(find.byKey(target)), const Offset(750.0, 0.0));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
|
expect(tester.getSize(find.byKey(target)), const Size(725.0, 600.0));
|
||||||
|
expect(tester.getTopRight(find.byKey(target)), const Offset(725.0, 0.0));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
expect(tester.getSize(find.byKey(target)), const Size(700.0, 600.0));
|
||||||
|
expect(tester.getTopRight(find.byKey(target)), const Offset(700.0, 0.0));
|
||||||
|
});
|
||||||
|
}
|
72
packages/flutter/test/widgets/safe_area_test.dart
Normal file
72
packages/flutter/test/widgets/safe_area_test.dart
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// 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:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('SafeArea - basic', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MediaQuery(
|
||||||
|
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
|
||||||
|
child: const SafeArea(
|
||||||
|
left: false,
|
||||||
|
child: const Placeholder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 20.0));
|
||||||
|
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SafeArea - nested', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MediaQuery(
|
||||||
|
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
|
||||||
|
child: const SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: const SafeArea(
|
||||||
|
right: false,
|
||||||
|
child: const Placeholder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
|
||||||
|
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SafeArea - changing', (WidgetTester tester) async {
|
||||||
|
final Widget child = const SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: const SafeArea(
|
||||||
|
left: false,
|
||||||
|
bottom: false,
|
||||||
|
child: const Placeholder(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MediaQuery(
|
||||||
|
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
|
||||||
|
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 600.0));
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MediaQuery(
|
||||||
|
data: const MediaQueryData(padding: const EdgeInsets.only(
|
||||||
|
left: 100.0,
|
||||||
|
top: 30.0,
|
||||||
|
right: 0.0,
|
||||||
|
bottom: 40.0,
|
||||||
|
)),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(100.0, 30.0));
|
||||||
|
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue