Last active
April 29, 2020 05:49
-
-
Save HansMuller/ef14543f27c9708a3ffa0f021e8c7817 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| This example defines a widget called "Destination" that displays two | |
| widgets in a column: an "icon" and a "label". The Destination widget's | |
| height is the height of the icon + the height of the label multiplied | |
| by the Destination's "progress" parameter, a value between 0 and 1. | |
| When progress is zero, the Destination's height is the same as the | |
| icon's height, and when progress is 1.0 the Destination's height is the | |
| sum of the icon and label's height | |
| Tapping on the demo animates the Destination's progress value. The | |
| Destination widget is clipped, so that only the part of the label exposed | |
| by the progress parameter appears. | |
| */ | |
| import 'dart:math' as math; | |
| import 'dart:ui' show lerpDouble; | |
| import 'package:flutter/foundation.dart'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/rendering.dart'; | |
| enum _DestinationSlot { | |
| icon, | |
| label, | |
| } | |
| class Destination extends RenderObjectWidget { | |
| const Destination({ Key key, this.icon, this.label, this.progress }) : super(key: key); | |
| final Widget icon; | |
| final Widget label; | |
| final double progress; | |
| @override | |
| _DestinationElement createElement() => _DestinationElement(this); | |
| @override | |
| _RenderDestination createRenderObject(BuildContext context) { | |
| return _RenderDestination(progress: progress); | |
| } | |
| @override | |
| void updateRenderObject(BuildContext context, _RenderDestination renderObject) { | |
| renderObject.progress = progress; | |
| } | |
| } | |
| class _DestinationElement extends RenderObjectElement { | |
| _DestinationElement(Destination widget) : super(widget); | |
| final Map<_DestinationSlot, Element> slotToChild = <_DestinationSlot, Element>{}; | |
| final Map<Element, _DestinationSlot> childToSlot = <Element, _DestinationSlot>{}; | |
| @override | |
| Destination get widget => super.widget as Destination; | |
| @override | |
| _RenderDestination get renderObject => super.renderObject as _RenderDestination; | |
| @override | |
| void visitChildren(ElementVisitor visitor) { | |
| slotToChild.values.forEach(visitor); | |
| } | |
| @override | |
| void forgetChild(Element child) { | |
| assert(slotToChild.values.contains(child)); | |
| assert(childToSlot.keys.contains(child)); | |
| final _DestinationSlot slot = childToSlot[child]; | |
| childToSlot.remove(child); | |
| slotToChild.remove(slot); | |
| super.forgetChild(child); | |
| } | |
| void _mountChild(Widget widget, _DestinationSlot slot) { | |
| final Element oldChild = slotToChild[slot]; | |
| final Element newChild = updateChild(oldChild, widget, slot); | |
| if (oldChild != null) { | |
| slotToChild.remove(slot); | |
| childToSlot.remove(oldChild); | |
| } | |
| if (newChild != null) { | |
| slotToChild[slot] = newChild; | |
| childToSlot[newChild] = slot; | |
| } | |
| } | |
| @override | |
| void mount(Element parent, dynamic newSlot) { | |
| super.mount(parent, newSlot); | |
| _mountChild(widget.icon, _DestinationSlot.icon); | |
| _mountChild(widget.label, _DestinationSlot.label); | |
| } | |
| void _updateChild(Widget widget, _DestinationSlot slot) { | |
| final Element oldChild = slotToChild[slot]; | |
| final Element newChild = updateChild(oldChild, widget, slot); | |
| if (oldChild != null) { | |
| childToSlot.remove(oldChild); | |
| slotToChild.remove(slot); | |
| } | |
| if (newChild != null) { | |
| slotToChild[slot] = newChild; | |
| childToSlot[newChild] = slot; | |
| } | |
| } | |
| @override | |
| void update(Destination newWidget) { | |
| super.update(newWidget); | |
| assert(widget == newWidget); | |
| _updateChild(widget.icon, _DestinationSlot.icon); | |
| _updateChild(widget.label, _DestinationSlot.label); | |
| } | |
| void _updateRenderObject(RenderBox child, _DestinationSlot slot) { | |
| switch (slot) { | |
| case _DestinationSlot.icon: | |
| renderObject.icon = child; | |
| break; | |
| case _DestinationSlot.label: | |
| renderObject.label = child; | |
| break; | |
| } | |
| } | |
| @override | |
| void insertChildRenderObject(RenderObject child, dynamic slotValue) { | |
| assert(child is RenderBox); | |
| assert(slotValue is _DestinationSlot); | |
| final _DestinationSlot slot = slotValue as _DestinationSlot; | |
| _updateRenderObject(child as RenderBox, slot); | |
| assert(renderObject.childToSlot.keys.contains(child)); | |
| assert(renderObject.slotToChild.keys.contains(slot)); | |
| } | |
| @override | |
| void removeChildRenderObject(RenderObject child) { | |
| assert(child is RenderBox); | |
| assert(renderObject.childToSlot.keys.contains(child)); | |
| _updateRenderObject(null, renderObject.childToSlot[child]); | |
| assert(!renderObject.childToSlot.keys.contains(child)); | |
| assert(!renderObject.slotToChild.keys.contains(slot)); | |
| } | |
| @override | |
| void moveChildRenderObject(RenderObject child, dynamic slotValue) { | |
| assert(false, 'not reachable'); | |
| } | |
| } | |
| class _RenderDestination extends RenderBox { | |
| _RenderDestination({ @required double progress }) : assert(progress != null), _progress = progress; | |
| final Map<_DestinationSlot, RenderBox> slotToChild = <_DestinationSlot, RenderBox>{}; | |
| final Map<RenderBox, _DestinationSlot> childToSlot = <RenderBox, _DestinationSlot>{}; | |
| RenderBox _updateChild(RenderBox oldChild, RenderBox newChild, _DestinationSlot slot) { | |
| if (oldChild != null) { | |
| dropChild(oldChild); | |
| childToSlot.remove(oldChild); | |
| slotToChild.remove(slot); | |
| } | |
| if (newChild != null) { | |
| childToSlot[newChild] = slot; | |
| slotToChild[slot] = newChild; | |
| adoptChild(newChild); | |
| } | |
| return newChild; | |
| } | |
| RenderBox _icon; | |
| RenderBox get icon => _icon; | |
| set icon(RenderBox value) { | |
| _icon = _updateChild(_icon, value, _DestinationSlot.icon); | |
| } | |
| RenderBox _label; | |
| RenderBox get label => _label; | |
| set label(RenderBox value) { | |
| _label = _updateChild(_label, value, _DestinationSlot.label); | |
| } | |
| double get progress => _progress; | |
| double _progress; | |
| set progress(double value) { | |
| assert(value != null); | |
| if (_progress == value) | |
| return; | |
| _progress = value; | |
| markNeedsLayout(); | |
| } | |
| // The returned list is ordered for hit testing. | |
| Iterable<RenderBox> get _children sync* { | |
| if (icon != null) | |
| yield icon; | |
| if (label != null) | |
| yield label; | |
| } | |
| @override | |
| void attach(PipelineOwner owner) { | |
| super.attach(owner); | |
| for (final RenderBox child in _children) | |
| child.attach(owner); | |
| } | |
| @override | |
| void detach() { | |
| super.detach(); | |
| for (final RenderBox child in _children) | |
| child.detach(); | |
| } | |
| @override | |
| void redepthChildren() { | |
| _children.forEach(redepthChild); | |
| } | |
| @override | |
| void visitChildren(RenderObjectVisitor visitor) { | |
| _children.forEach(visitor); | |
| } | |
| @override | |
| List<DiagnosticsNode> debugDescribeChildren() { | |
| final List<DiagnosticsNode> value = <DiagnosticsNode>[]; | |
| void add(RenderBox child, String name) { | |
| if (child != null) | |
| value.add(child.toDiagnosticsNode(name: name)); | |
| } | |
| add(icon, 'icon'); | |
| add(label, 'label'); | |
| return value; | |
| } | |
| @override | |
| bool get sizedByParent => false; | |
| static double _minWidth(RenderBox box, double height) { | |
| return box == null ? 0.0 : box.getMinIntrinsicWidth(height); | |
| } | |
| static double _maxWidth(RenderBox box, double height) { | |
| return box == null ? 0.0 : box.getMaxIntrinsicWidth(height); | |
| } | |
| @override | |
| double computeMinIntrinsicWidth(double height) { | |
| final double iconWidth = icon == null ? 0 : icon.getMinIntrinsicWidth(height); | |
| final double labelWidth = label == null ? 0 : label.getMinIntrinsicWidth(height); | |
| return math.max(iconWidth, labelWidth); | |
| } | |
| @override | |
| double computeMaxIntrinsicWidth(double height) { | |
| final double iconWidth = icon == null ? 0 : icon.getMaxIntrinsicWidth(height); | |
| final double labelWidth = label == null ? 0 : label.getMaxIntrinsicWidth(height); | |
| return math.max(iconWidth, labelWidth); | |
| } | |
| @override | |
| double computeMinIntrinsicHeight(double width) { | |
| final double iconHeight = icon == null ? 0 : icon.getMaxIntrinsicHeight(width); | |
| final double labelHeight = label == null ? 0 : label.getMaxIntrinsicHeight(width); | |
| return iconHeight + labelHeight; | |
| } | |
| @override | |
| double computeMaxIntrinsicHeight(double width) { | |
| return computeMinIntrinsicHeight(width); | |
| } | |
| static Size _layoutBox(RenderBox box, BoxConstraints constraints) { | |
| if (box == null) | |
| return Size.zero; | |
| box.layout(constraints, parentUsesSize: true); | |
| return box.size; | |
| } | |
| static void _positionBox(RenderBox box, Offset offset) { | |
| final BoxParentData parentData = box.parentData as BoxParentData; | |
| parentData.offset = offset; | |
| } | |
| @override | |
| void performLayout() { | |
| final Size iconSize = _layoutBox(icon, constraints); | |
| final Size labelSize = _layoutBox(label, constraints); | |
| final double width = math.max(iconSize.width, labelSize.width); | |
| final double height = iconSize.height + labelSize.height * progress; | |
| _positionBox(icon, Offset((width - iconSize.width) / 2, 0)); | |
| _positionBox(label, Offset((width - labelSize.width) / 2, iconSize.height)); | |
| size = constraints.constrain(Size(width, height)); | |
| assert(size.width == constraints.constrainWidth(width)); | |
| assert(size.height == constraints.constrainHeight(height)); | |
| } | |
| @override | |
| void paint(PaintingContext context, Offset offset) { | |
| void doPaint(RenderBox child) { | |
| if (child != null) { | |
| final BoxParentData parentData = child.parentData as BoxParentData; | |
| context.paintChild(child, parentData.offset + offset); | |
| } | |
| } | |
| doPaint(icon); | |
| doPaint(label); | |
| } | |
| @override | |
| bool hitTestSelf(Offset position) => true; | |
| @override | |
| bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) { | |
| assert(position != null); | |
| for (final RenderBox child in _children) { | |
| final BoxParentData parentData = child.parentData as BoxParentData; | |
| final bool isHit = result.addWithPaintOffset( | |
| offset: parentData.offset, | |
| position: position, | |
| hitTest: (BoxHitTestResult result, Offset transformed) { | |
| assert(transformed == position - parentData.offset); | |
| return child.hitTest(result, position: transformed); | |
| }, | |
| ); | |
| if (isHit) | |
| return true; | |
| } | |
| return false; | |
| } | |
| } | |
| class AnimationDemo extends StatefulWidget { | |
| @override | |
| _AnimationDemoState createState() => _AnimationDemoState(); | |
| } | |
| class _AnimationDemoState extends State<AnimationDemo> with SingleTickerProviderStateMixin { | |
| AnimationController _controller; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _controller = new AnimationController( | |
| duration: const Duration(milliseconds: 2000), | |
| vsync: this | |
| ); | |
| } | |
| @override | |
| void dispose() { | |
| _controller.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return GestureDetector( | |
| onTap: () { | |
| if (_controller.isCompleted) { | |
| _controller.reverse(); | |
| } else { | |
| _controller.forward(); | |
| } | |
| }, | |
| child: Scaffold( | |
| body: Center( | |
| child: DefaultTextStyle( | |
| style: Theme.of(context).textTheme.display2, | |
| child: Container( | |
| color: Colors.grey.withOpacity(0.15), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| children: <Widget>[ | |
| Text('Above'), | |
| Container( | |
| color: Colors.blue.withOpacity(0.15), | |
| child: AnimatedBuilder( | |
| animation: _controller, | |
| builder: (BuildContext context, Widget child) { | |
| return ClipRect( | |
| child: Destination( | |
| icon: Icon(Icons.android, size: 48), | |
| label: Text('Hello World'), // Try 'Hello\nWorld' instead | |
| progress: _controller.value, | |
| ), | |
| ); | |
| }, | |
| ), | |
| ), | |
| Text('Below'), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| void main() { | |
| runApp(MaterialApp(title: 'Reveal Transition Demo', home: AnimationDemo())); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment