Skip to content

Commit c3a9950

Browse files
Merge pull request #1871 from EnsembleUI/header-enhancement
added animated Header
2 parents e28873a + d799710 commit c3a9950

File tree

2 files changed

+157
-14
lines changed

2 files changed

+157
-14
lines changed

modules/ensemble/lib/framework/view/page.dart

+156-13
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ class PageState extends State<Page>
286286
dynamic appBar = _buildAppBar(pageModel.headerModel!,
287287
scrollableView: true,
288288
showNavigationIcon: pageModel.runtimeStyles?['showNavigationIcon']);
289-
if (appBar is SliverAppBar) {
289+
if (appBar is SliverAppBar || appBar is AnimatedAppBar) {
290290
return appBar;
291291
}
292292
}
@@ -319,9 +319,12 @@ class PageState extends State<Page>
319319
dynamic _buildAppBar(HeaderModel headerModel,
320320
{required bool scrollableView, bool? showNavigationIcon}) {
321321
Widget? titleWidget;
322+
323+
322324
if (headerModel.titleWidget != null) {
323325
titleWidget = _scopeManager.buildWidget(headerModel.titleWidget!);
324326
}
327+
325328
if (titleWidget == null && headerModel.titleText != null) {
326329
final title = _scopeManager.dataContext.eval(headerModel.titleText);
327330
titleWidget = Text(Utils.translate(title.toString(), context));
@@ -351,15 +354,29 @@ class PageState extends State<Page>
351354
Color? shadowColor = Utils.getColor(evaluatedHeader?['shadowColor']);
352355
double? elevation =
353356
Utils.optionalInt(evaluatedHeader?['elevation'], min: 0)?.toDouble();
354-
357+
ScrollMode scrollMode =
358+
Utils.getEnum<ScrollMode>(evaluatedHeader?['scrollMode'], ScrollMode.values);
355359
final titleBarHeight =
356360
Utils.optionalInt(evaluatedHeader?['titleBarHeight'], min: 0)
357361
?.toDouble() ??
358362
kToolbarHeight;
359363

364+
// animation
365+
final animation = evaluatedHeader?['animation'] != null
366+
? EnsembleThemeManager.yamlToDart(evaluatedHeader?['animation'])
367+
: null;
368+
bool animationEnabled = false;
369+
int? duration;
370+
Curve? curve;
371+
if (animation != null) {
372+
animationEnabled = Utils.getBool(animation!['enabled'], fallback: false);
373+
duration = Utils.getInt(animation!['duration'], fallback: 0);
374+
curve = Utils.getCurve(animation!['curve']);
375+
}
360376
// applicable only to Sliver scrolling
361377
double? flexibleMaxHeight =
362378
Utils.optionalInt(evaluatedHeader?['flexibleMaxHeight'])?.toDouble();
379+
363380
double? flexibleMinHeight =
364381
Utils.optionalInt(evaluatedHeader?['flexibleMinHeight'])?.toDouble();
365382
// collapsed height if specified needs to be bigger than titleBar height
@@ -368,28 +385,32 @@ class PageState extends State<Page>
368385
}
369386

370387
if (scrollableView) {
371-
return SliverAppBar(
388+
return AnimatedAppBar( scrollController: externalScrollController!,
372389
automaticallyImplyLeading:
373-
leadingWidget == null && showNavigationIcon != false,
374-
leading: leadingWidget,
375-
title: titleWidget,
390+
leadingWidget == null && showNavigationIcon != false,
391+
leadingWidget: leadingWidget,
392+
titleWidget: titleWidget,
376393
centerTitle: centerTitle,
377394
backgroundColor: backgroundColor,
378395
surfaceTintColor: surfaceTintColor,
379396
foregroundColor: color,
397+
animated: animationEnabled,
380398

381399
// control the drop shadow on the header's bottom edge
382400
elevation: elevation,
383401
shadowColor: shadowColor,
384402

385-
toolbarHeight: titleBarHeight,
403+
titleBarHeight: titleBarHeight,
404+
curve: curve,
405+
duration: duration,
406+
backgroundWidget: backgroundWidget,
407+
expandedBarHeight: flexibleMaxHeight,
408+
collapsedBarHeight: flexibleMinHeight,
409+
floating: scrollMode == ScrollMode.floating,
410+
pinned: scrollMode == ScrollMode.pinned,
386411

387-
flexibleSpace: wrapsInFlexible(backgroundWidget),
388-
expandedHeight: flexibleMaxHeight,
389-
collapsedHeight: flexibleMinHeight,
390-
391-
pinned: true,
392412
);
413+
393414
} else {
394415
return AppBar(
395416
automaticallyImplyLeading:
@@ -583,9 +604,10 @@ class PageState extends State<Page>
583604

584605
Widget buildScrollablePageContent(bool hasDrawer) {
585606
List<Widget> slivers = [];
586-
607+
externalScrollController = ScrollController();
587608
// appBar
588609
Widget? appBar = buildSliverAppBar(widget._pageModel, hasDrawer);
610+
589611
if (appBar != null) {
590612
slivers.add(appBar);
591613
}
@@ -840,6 +862,127 @@ class PageState extends State<Page>
840862
}
841863
}
842864

865+
class AnimatedAppBar extends StatefulWidget {
866+
final ScrollController scrollController;
867+
final collapsedBarHeight;
868+
final expandedBarHeight;
869+
final automaticallyImplyLeading;
870+
final leadingWidget;
871+
final titleWidget;
872+
final centerTitle;
873+
final backgroundColor;
874+
final surfaceTintColor;
875+
final foregroundColor;
876+
final elevation;
877+
final shadowColor;
878+
final titleBarHeight;
879+
final backgroundWidget;
880+
final floating;
881+
final pinned;
882+
final animated;
883+
final curve;
884+
final duration;
885+
AnimatedAppBar(
886+
{Key? key,
887+
this.automaticallyImplyLeading,
888+
this.leadingWidget,
889+
this.titleWidget,
890+
this.centerTitle,
891+
this.backgroundColor,
892+
this.surfaceTintColor,
893+
this.foregroundColor,
894+
this.elevation,
895+
this.shadowColor,
896+
this.titleBarHeight,
897+
this.backgroundWidget,
898+
this.animated,
899+
this.floating,
900+
this.pinned,
901+
this.collapsedBarHeight,
902+
this.expandedBarHeight,
903+
required this.scrollController,
904+
this.curve,
905+
this.duration})
906+
: super(key: key);
907+
908+
@override
909+
_AnimatedAppBarState createState() => _AnimatedAppBarState();
910+
}
911+
912+
class _AnimatedAppBarState extends State<AnimatedAppBar> {
913+
bool isCollapsed = false;
914+
915+
@override
916+
void initState() {
917+
super.initState();
918+
widget.scrollController.addListener(_updateCollapseState);
919+
}
920+
921+
void _updateCollapseState() {
922+
bool newState = widget.scrollController.hasClients &&
923+
widget.scrollController.offset >
924+
(widget.expandedBarHeight - widget.collapsedBarHeight);
925+
926+
if (newState != isCollapsed) {
927+
setState(() {
928+
isCollapsed = newState;
929+
});
930+
}
931+
}
932+
933+
@override
934+
void dispose() {
935+
widget.scrollController.removeListener(_updateCollapseState);
936+
super.dispose();
937+
}
938+
939+
/// wraps the background in a FlexibleSpaceBar for automatic stretching and parallax effect.
940+
Widget? wrapsInFlexible(Widget? backgroundWidget) {
941+
if (backgroundWidget != null) {
942+
return FlexibleSpaceBar(
943+
background: backgroundWidget,
944+
collapseMode: CollapseMode.parallax,
945+
);
946+
}
947+
return null;
948+
}
949+
950+
@override
951+
Widget build(BuildContext context) {
952+
return SliverAppBar(
953+
collapsedHeight: widget.collapsedBarHeight,
954+
expandedHeight: widget.expandedBarHeight,
955+
pinned: widget.pinned,
956+
centerTitle: widget.centerTitle,
957+
title: widget.animated
958+
? AnimatedContainer(
959+
curve: widget.curve ?? Curves.easeIn,
960+
duration: Duration(
961+
milliseconds: widget.duration ?? 300), // Animation duration
962+
transform: Matrix4.translationValues(
963+
0, // No horizontal movement
964+
isCollapsed ? 0 : -100, // Move from top to bottom
965+
0, // No depth movement
966+
),
967+
child: widget.titleWidget, // Your title widget
968+
) : widget.titleWidget,
969+
elevation: widget.elevation,
970+
backgroundColor: widget.backgroundColor,
971+
flexibleSpace: wrapsInFlexible(widget.backgroundWidget),
972+
automaticallyImplyLeading: widget.automaticallyImplyLeading,
973+
leading: widget.leadingWidget,
974+
surfaceTintColor: widget.surfaceTintColor,
975+
foregroundColor: widget.foregroundColor,
976+
shadowColor: widget.shadowColor,
977+
toolbarHeight: widget.titleBarHeight,
978+
);
979+
}
980+
}
981+
982+
enum ScrollMode {
983+
floating,
984+
pinned,
985+
}
843986
class ActionResponse {
844987
Map<String, dynamic>? _resultData;
845988
Set<Function> listeners = {};

modules/ensemble/lib/page_model.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ class SinglePageModel extends PageModel with HasStyles {
398398
titleText: titleText,
399399
titleWidget: titleWidget,
400400
flexibleBackground: background,
401-
leadingWidget: leadingWidget,
401+
leadingWidget: leadingWidget,
402402
inlineStyles: styles,
403403
classList: classList);
404404
}

0 commit comments

Comments
 (0)