Add animated opacity to the flex layout children. (#1390)
diff --git a/packages/devtools_app/lib/src/inspector/flutter/story_of_your_layout/flex.dart b/packages/devtools_app/lib/src/inspector/flutter/story_of_your_layout/flex.dart index 45e7bad..8d585a5 100644 --- a/packages/devtools_app/lib/src/inspector/flutter/story_of_your_layout/flex.dart +++ b/packages/devtools_app/lib/src/inspector/flutter/story_of_your_layout/flex.dart
@@ -25,10 +25,14 @@ const arrowStrokeWidth = 1.5; /// Hardcoded sizes for scaling the flex children widget properly. -const minRenderWidth = 215.0; -const minRenderHeight = 275.0; -const defaultMaxRenderWidth = 300.0; -const defaultMaxRenderHeight = 300.0; +const minRenderWidth = 250.0; +const minRenderHeight = 300.0; + +/// The size to shrink a widget by when animating it in. +const entranceMargin = 50.0; + +const defaultMaxRenderWidth = 400.0; +const defaultMaxRenderHeight = 400.0; const widgetTitleMaxWidthPercentage = 0.75; @@ -76,6 +80,8 @@ const freeSpaceAssetName = 'assets/img/story_of_layout/empty_space.png'; +const entranceAnimationDuration = Duration(milliseconds: 500); + class StoryOfYourFlexWidget extends StatefulWidget { const StoryOfYourFlexWidget( this.properties, { @@ -96,16 +102,67 @@ _StoryOfYourFlexWidgetState createState() => _StoryOfYourFlexWidgetState(); } -class _StoryOfYourFlexWidgetState extends State<StoryOfYourFlexWidget> { +class _StoryOfYourFlexWidgetState extends State<StoryOfYourFlexWidget> + with TickerProviderStateMixin { + @override + void initState() { + super.initState(); + entranceController = AnimationController( + vsync: this, + duration: entranceAnimationDuration, + )..addStatusListener((status) { + if (status == AnimationStatus.dismissed) { + setState(() { + _previousProperties = null; + entranceController.forward(); + }); + } + }); + expandedEntrance = + CurvedAnimation(parent: entranceController, curve: Curves.easeIn); + allEntrance = + CurvedAnimation(parent: entranceController, curve: Curves.easeIn); + _updateProperties(); + } + + @override + void dispose() { + entranceController.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(StoryOfYourFlexWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.properties.node != widget.properties.node) { + _previousProperties = oldWidget.properties; + } + _updateProperties(); + } + + void _updateProperties() { + if (_previousProperties != null) { + entranceController.reverse(); + } else { + entranceController.forward(); + } + } + + AnimationController entranceController; + CurvedAnimation expandedEntrance; + CurvedAnimation allEntrance; + Size get size => properties.size; - FlexLayoutProperties get properties => widget.properties; + FlexLayoutProperties get properties => + _previousProperties ?? widget.properties; + FlexLayoutProperties _previousProperties; - List<LayoutProperties> get children => widget.properties.children; + List<LayoutProperties> get children => properties.children; - Axis get direction => widget.properties.direction; + Axis get direction => properties.direction; - bool get isRow => properties.direction == Axis.horizontal; + bool get isRow => direction == Axis.horizontal; bool get isColumn => !isRow; @@ -192,16 +249,16 @@ ); } - Function _onTap(LayoutProperties properties) => () async { - final controller = widget.inspectorController; - final diagnostic = properties.node; - controller.refreshSelection(diagnostic, diagnostic, false); - final inspectorService = await diagnostic.inspectorService; - await inspectorService.setSelectionInspector( - diagnostic.valueRef, - true, - ); - }; + Future<void> _onTap(LayoutProperties properties) async { + final controller = widget.inspectorController; + final diagnostic = properties.node; + controller.refreshSelection(diagnostic, diagnostic, false); + final inspectorService = await diagnostic.inspectorService; + await inspectorService.setSelectionInspector( + diagnostic.valueRef, + true, + ); + } Widget _visualizeChild({ LayoutProperties childProperties, @@ -211,58 +268,87 @@ Size renderSize, Offset renderOffset, }) { + Widget buildEntranceAnimation(BuildContext context, Widget child) { + final vertical = properties.isMainAxisVertical; + final horizontal = properties.isMainAxisHorizontal; + Size size = renderSize; + if (childProperties.flexFactor != null) { + size = SizeTween( + begin: Size( + horizontal ? minRenderWidth - entranceMargin : renderSize.width, + vertical ? minRenderHeight - entranceMargin : renderSize.height, + ), + end: renderSize, + ).evaluate(expandedEntrance); + } + return Opacity( + opacity: min([allEntrance.value * 5, 1.0]), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: (renderSize.width - size.width) / 2, + vertical: (renderSize.height - size.height) / 2, + ), + child: child, + ), + ); + } + final flexFactor = childProperties.flexFactor; return Positioned( top: renderOffset.dy, left: renderOffset.dx, child: InkWell( - onTap: _onTap(childProperties), - child: Container( + onTap: () => _onTap(childProperties), + child: SizedBox( width: renderSize.width, height: renderSize.height, - child: WidgetVisualizer( - backgroundColor: backgroundColor, - title: childProperties.description, - borderColor: borderColor, - textColor: textColor, - child: _visualizeWidthAndHeightWithConstraints( - arrowHeadSize: arrowHeadSize, - widget: Align( - alignment: Alignment.topRight, - child: Container( - margin: const EdgeInsets.only( - top: margin, - left: margin, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.end, - children: <Widget>[ - Text( - 'flex: $flexFactor', - style: TextStyle(fontWeight: FontWeight.bold), - ), - if (flexFactor == 0 || flexFactor == null) + child: AnimatedBuilder( + animation: entranceController, + builder: buildEntranceAnimation, + child: WidgetVisualizer( + backgroundColor: backgroundColor, + title: childProperties.description, + borderColor: borderColor, + textColor: textColor, + child: _visualizeWidthAndHeightWithConstraints( + arrowHeadSize: arrowHeadSize, + widget: Align( + alignment: Alignment.topRight, + child: Container( + margin: const EdgeInsets.only( + top: margin, + left: margin, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: <Widget>[ Text( - 'unconstrained ${isRow ? 'horizontal' : 'vertical'}', - style: TextStyle( - color: ThemedColor( - const Color(0xFFD08A29), - Colors.orange.shade700, - ), - fontStyle: FontStyle.italic, - ), - maxLines: 2, - softWrap: true, - overflow: TextOverflow.ellipsis, - textScaleFactor: smallTextScaleFactor, - textAlign: TextAlign.right, + 'flex: $flexFactor', + style: TextStyle(fontWeight: FontWeight.bold), ), - ], + if (flexFactor == 0 || flexFactor == null) + Text( + 'unconstrained ${isRow ? 'horizontal' : 'vertical'}', + style: TextStyle( + color: ThemedColor( + const Color(0xFFD08A29), + Colors.orange.shade700, + ), + fontStyle: FontStyle.italic, + ), + maxLines: 2, + softWrap: true, + overflow: TextOverflow.ellipsis, + textScaleFactor: smallTextScaleFactor, + textAlign: TextAlign.right, + ), + ], + ), ), ), + properties: childProperties, ), - properties: childProperties, ), ), ), @@ -337,7 +423,6 @@ ), ) ]; - return SingleChildScrollView( scrollDirection: properties.direction, child: ConstrainedBox( @@ -352,7 +437,7 @@ ? sum(childrenAndMainAxisSpacesRenderProps .map((renderSize) => renderSize.height)) : maxHeight, - ), + ).normalize(), child: Stack( children: [ Positioned.fill( @@ -532,7 +617,7 @@ left: crossAxisArrowIndicatorSize + margin, ), child: InkWell( - onTap: _onTap(properties), + onTap: () => _onTap(properties), child: WidgetVisualizer( title: flexType, backgroundColor: widget.highlightChild == null
diff --git a/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_column_layout.png b/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_column_layout.png index eb62a60..b900850 100644 --- a/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_column_layout.png +++ b/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_column_layout.png Binary files differ
diff --git a/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_row_layout.png b/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_row_layout.png index 1332cd9..a2ac431 100644 --- a/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_row_layout.png +++ b/packages/devtools_app/test/flutter/story_of_layout/goldens/story_of_row_layout.png Binary files differ