From 42871c181497da50708e09b2145e5dbbdca9fe6c Mon Sep 17 00:00:00 2001 From: proformance Date: Sat, 27 Jul 2024 20:21:31 +0200 Subject: [PATCH 1/2] fix: add flexibleSpace animation with fade transition --- lib/src/app_bar/app_bar.dart | 3 +- lib/src/app_bar/flexible.dart | 107 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 lib/src/app_bar/flexible.dart diff --git a/lib/src/app_bar/app_bar.dart b/lib/src/app_bar/app_bar.dart index ff477eb..fffe778 100644 --- a/lib/src/app_bar/app_bar.dart +++ b/lib/src/app_bar/app_bar.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'actions.dart'; import 'bottom.dart'; +import 'flexible.dart'; import 'leading.dart'; import 'state.dart'; import 'title.dart'; @@ -247,7 +248,7 @@ class _AnimatedAppBar extends AnimatedWidget { automaticallyImplyLeading: false, title: AnimatedTitle(state), actions: [AnimatedActions(state)], - // TODO(JonasWanke): Animate `flexibleSpace` + flexibleSpace: AnimatedFlexibleSpace(state), bottom: AnimatedBottom(state), elevation: state.elevation, scrolledUnderElevation: state.scrolledUnderElevation, diff --git a/lib/src/app_bar/flexible.dart b/lib/src/app_bar/flexible.dart new file mode 100644 index 0000000..27dad08 --- /dev/null +++ b/lib/src/app_bar/flexible.dart @@ -0,0 +1,107 @@ +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:black_hole_flutter/black_hole_flutter.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'app_bar.dart'; +import 'state.dart'; + +class AnimatedFlexibleSpace extends MultiChildRenderObjectWidget { + AnimatedFlexibleSpace(MorphingState state) + : t = state.t, + super( + children: [_createChild(state.parent), _createChild(state.child)], + ); + + final double t; + + static Widget _createChild(EndState state) { + final flexibleSpace = state.appBar.flexibleSpace; + if (flexibleSpace == null) return const SizedBox(); + + return DefaultTextStyle.merge(child: flexibleSpace); + } + + @override + RenderObject createRenderObject(BuildContext context) => + _AnimatedFlexibleSpaceLayout(t: t); + + @override + void updateRenderObject( + BuildContext context, + covariant RenderObject renderObject, + ) => + (renderObject as _AnimatedFlexibleSpaceLayout).t = t; +} + +class _AnimatedFlexibleSpaceParentData + extends ContainerBoxParentData {} + +class _AnimatedFlexibleSpaceLayout + extends AnimatedAppBarLayout<_AnimatedFlexibleSpaceParentData> { + _AnimatedFlexibleSpaceLayout({super.t}); + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! _AnimatedFlexibleSpaceParentData) { + child.parentData = _AnimatedFlexibleSpaceParentData(); + } + } + + @override + double computeMinIntrinsicWidth(double height) => + children.map((c) => c.getMinIntrinsicWidth(height)).max.toDouble(); + @override + double computeMaxIntrinsicWidth(double height) => + children.map((c) => c.getMaxIntrinsicWidth(height)).max.toDouble(); + @override + double computeMinIntrinsicHeight(double width) => + children.map((c) => c.getMinIntrinsicHeight(width)).max.toDouble(); + @override + double computeMaxIntrinsicHeight(double width) => + children.map((c) => c.getMaxIntrinsicHeight(width)).max.toDouble(); + + @override + bool get alwaysNeedsCompositing => true; + + @override + void performLayout() { + assert(!sizedByParent); + + final parent = firstChild!; + final child = parent.data.nextSibling!; + + parent.layout(constraints, parentUsesSize: true); + child.layout(constraints, parentUsesSize: true); + size = parent.size.coerceAtLeast(child.size); + + parent.data.offset = Offset(0, (size.height - parent.size.height) / 2); + child.data.offset = Offset(0, (size.height - child.size.height) / 2); + } + + @override + void paint(PaintingContext context, Offset offset) { + final parent = firstChild!; + final child = parent.data.nextSibling!; + + context + ..pushOpacity( + offset, + math.max(0, 1 - t).opacityToAlpha, + (context, offset) => context.paintChild(parent, offset), + ) + ..pushOpacity( + offset, + math.max(0, t).opacityToAlpha, + (context, offset) => context.paintChild(child, offset), + ); + } +} + +extension _ParentData on RenderBox { + _AnimatedFlexibleSpaceParentData get data => + parentData! as _AnimatedFlexibleSpaceParentData; +} From 88a2b7e4cef1cdc9e99534faba49a4538a2c5854 Mon Sep 17 00:00:00 2001 From: proformance Date: Sat, 27 Jul 2024 20:53:36 +0200 Subject: [PATCH 2/2] fix: better demostrate awesomeness of swipeable_page_route --- example/lib/main.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index 74fd8ec..3c43303 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -53,7 +53,19 @@ class FirstPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: MorphingAppBar( + leading: Icon(Icons.menu), title: const Text('🔙 swipeable_page_route example'), + flexibleSpace: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color.fromARGB(255, 0, 92, 157), + Color(0xff0078C1), + Color.fromARGB(255, 9, 149, 224), + ], + ), + ), + ), ), body: Center( child: ElevatedButton( @@ -90,6 +102,17 @@ class _SecondPageState extends State { return Scaffold( appBar: MorphingAppBar( title: const Text('Page 2'), + flexibleSpace: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffFF5733), + Color(0xffC70039), + Color(0xff900C3F), + ], + ), + ), + ), actions: [ IconButton( key: const ValueKey('check'),