Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added animated Header #1871

Merged
merged 16 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 156 additions & 13 deletions modules/ensemble/lib/framework/view/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ class PageState extends State<Page>
dynamic appBar = _buildAppBar(pageModel.headerModel!,
scrollableView: true,
showNavigationIcon: pageModel.runtimeStyles?['showNavigationIcon']);
if (appBar is SliverAppBar) {
if (appBar is SliverAppBar || appBar is AnimatedAppBar) {
return appBar;
}
}
Expand Down Expand Up @@ -319,9 +319,12 @@ class PageState extends State<Page>
dynamic _buildAppBar(HeaderModel headerModel,
{required bool scrollableView, bool? showNavigationIcon}) {
Widget? titleWidget;


if (headerModel.titleWidget != null) {
titleWidget = _scopeManager.buildWidget(headerModel.titleWidget!);
}

if (titleWidget == null && headerModel.titleText != null) {
final title = _scopeManager.dataContext.eval(headerModel.titleText);
titleWidget = Text(Utils.translate(title.toString(), context));
Expand Down Expand Up @@ -351,15 +354,29 @@ class PageState extends State<Page>
Color? shadowColor = Utils.getColor(evaluatedHeader?['shadowColor']);
double? elevation =
Utils.optionalInt(evaluatedHeader?['elevation'], min: 0)?.toDouble();

ScrollMode scrollMode =
Utils.getEnum<ScrollMode>(evaluatedHeader?['scrollMode'], ScrollMode.values);
final titleBarHeight =
Utils.optionalInt(evaluatedHeader?['titleBarHeight'], min: 0)
?.toDouble() ??
kToolbarHeight;

// animation
final animation = evaluatedHeader?['animation'] != null
? EnsembleThemeManager.yamlToDart(evaluatedHeader?['animation'])
: null;
bool animationEnabled = false;
int? duration;
Curve? curve;
if (animation != null) {
animationEnabled = Utils.getBool(animation!['enabled'], fallback: false);
duration = Utils.getInt(animation!['duration'], fallback: 0);
curve = Utils.getCurve(animation!['curve']);
}
// applicable only to Sliver scrolling
double? flexibleMaxHeight =
Utils.optionalInt(evaluatedHeader?['flexibleMaxHeight'])?.toDouble();

double? flexibleMinHeight =
Utils.optionalInt(evaluatedHeader?['flexibleMinHeight'])?.toDouble();
// collapsed height if specified needs to be bigger than titleBar height
Expand All @@ -368,28 +385,32 @@ class PageState extends State<Page>
}

if (scrollableView) {
return SliverAppBar(
return AnimatedAppBar( scrollController: externalScrollController!,
automaticallyImplyLeading:
leadingWidget == null && showNavigationIcon != false,
leading: leadingWidget,
title: titleWidget,
leadingWidget == null && showNavigationIcon != false,
leadingWidget: leadingWidget,
titleWidget: titleWidget,
centerTitle: centerTitle,
backgroundColor: backgroundColor,
surfaceTintColor: surfaceTintColor,
foregroundColor: color,
animated: animationEnabled,

// control the drop shadow on the header's bottom edge
elevation: elevation,
shadowColor: shadowColor,

toolbarHeight: titleBarHeight,
titleBarHeight: titleBarHeight,
curve: curve,
duration: duration,
backgroundWidget: backgroundWidget,
expandedBarHeight: flexibleMaxHeight,
collapsedBarHeight: flexibleMinHeight,
floating: scrollMode == ScrollMode.floating,
pinned: scrollMode == ScrollMode.pinned,

flexibleSpace: wrapsInFlexible(backgroundWidget),
expandedHeight: flexibleMaxHeight,
collapsedHeight: flexibleMinHeight,

pinned: true,
);

} else {
return AppBar(
automaticallyImplyLeading:
Expand Down Expand Up @@ -583,9 +604,10 @@ class PageState extends State<Page>

Widget buildScrollablePageContent(bool hasDrawer) {
List<Widget> slivers = [];

externalScrollController = ScrollController();
// appBar
Widget? appBar = buildSliverAppBar(widget._pageModel, hasDrawer);

if (appBar != null) {
slivers.add(appBar);
}
Expand Down Expand Up @@ -840,6 +862,127 @@ class PageState extends State<Page>
}
}

class AnimatedAppBar extends StatefulWidget {
final ScrollController scrollController;
final collapsedBarHeight;
final expandedBarHeight;
final automaticallyImplyLeading;
final leadingWidget;
final titleWidget;
final centerTitle;
final backgroundColor;
final surfaceTintColor;
final foregroundColor;
final elevation;
final shadowColor;
final titleBarHeight;
final backgroundWidget;
final floating;
final pinned;
final animated;
final curve;
final duration;
AnimatedAppBar(
{Key? key,
this.automaticallyImplyLeading,
this.leadingWidget,
this.titleWidget,
this.centerTitle,
this.backgroundColor,
this.surfaceTintColor,
this.foregroundColor,
this.elevation,
this.shadowColor,
this.titleBarHeight,
this.backgroundWidget,
this.animated,
this.floating,
this.pinned,
this.collapsedBarHeight,
this.expandedBarHeight,
required this.scrollController,
this.curve,
this.duration})
: super(key: key);

@override
_AnimatedAppBarState createState() => _AnimatedAppBarState();
}

class _AnimatedAppBarState extends State<AnimatedAppBar> {
bool isCollapsed = false;

@override
void initState() {
super.initState();
widget.scrollController.addListener(_updateCollapseState);
}

void _updateCollapseState() {
bool newState = widget.scrollController.hasClients &&
widget.scrollController.offset >
(widget.expandedBarHeight - widget.collapsedBarHeight);

if (newState != isCollapsed) {
setState(() {
isCollapsed = newState;
});
}
}

@override
void dispose() {
widget.scrollController.removeListener(_updateCollapseState);
super.dispose();
}

/// wraps the background in a FlexibleSpaceBar for automatic stretching and parallax effect.
Widget? wrapsInFlexible(Widget? backgroundWidget) {
if (backgroundWidget != null) {
return FlexibleSpaceBar(
background: backgroundWidget,
collapseMode: CollapseMode.parallax,
);
}
return null;
}

@override
Widget build(BuildContext context) {
return SliverAppBar(
collapsedHeight: widget.collapsedBarHeight,
expandedHeight: widget.expandedBarHeight,
pinned: widget.pinned,
centerTitle: widget.centerTitle,
title: widget.animated
? AnimatedContainer(
curve: widget.curve ?? Curves.easeIn,
duration: Duration(
milliseconds: widget.duration ?? 300), // Animation duration
transform: Matrix4.translationValues(
0, // No horizontal movement
isCollapsed ? 0 : -100, // Move from top to bottom
0, // No depth movement
),
child: widget.titleWidget, // Your title widget
) : widget.titleWidget,
elevation: widget.elevation,
backgroundColor: widget.backgroundColor,
flexibleSpace: wrapsInFlexible(widget.backgroundWidget),
automaticallyImplyLeading: widget.automaticallyImplyLeading,
leading: widget.leadingWidget,
surfaceTintColor: widget.surfaceTintColor,
foregroundColor: widget.foregroundColor,
shadowColor: widget.shadowColor,
toolbarHeight: widget.titleBarHeight,
);
}
}

enum ScrollMode {
floating,
pinned,
}
class ActionResponse {
Map<String, dynamic>? _resultData;
Set<Function> listeners = {};
Expand Down
2 changes: 1 addition & 1 deletion modules/ensemble/lib/page_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ class SinglePageModel extends PageModel with HasStyles {
titleText: titleText,
titleWidget: titleWidget,
flexibleBackground: background,
leadingWidget: leadingWidget,
leadingWidget: leadingWidget,
inlineStyles: styles,
classList: classList);
}
Expand Down