import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// https://github.com/flutter/flutter/issues/18345#issuecomment-1627644396 class DynamicSliverAppBar extends StatefulWidget { const DynamicSliverAppBar({ this.flexibleSpace, super.key, this.leading, this.automaticallyImplyLeading = true, this.title, this.actions, this.bottom, this.elevation, this.scrolledUnderElevation, this.shadowColor, this.surfaceTintColor, this.forceElevated = false, this.backgroundColor, this.backgroundGradient, this.foregroundColor, this.iconTheme, this.actionsIconTheme, this.primary = true, this.centerTitle, this.excludeHeaderSemantics = false, this.titleSpacing, this.collapsedHeight, this.expandedHeight, this.floating = false, this.pinned = false, this.snap = false, this.stretch = false, this.stretchTriggerOffset = 100.0, this.onStretchTrigger, this.shape, this.toolbarHeight = kToolbarHeight, this.leadingWidth, this.toolbarTextStyle, this.titleTextStyle, this.systemOverlayStyle, this.forceMaterialTransparency = false, this.clipBehavior, this.appBarClipper, this.hasTabBar = false, }); final bool hasTabBar; final Widget? flexibleSpace; final Widget? leading; final bool automaticallyImplyLeading; final Widget? title; final List? actions; final PreferredSizeWidget? bottom; final double? elevation; final double? scrolledUnderElevation; final Color? shadowColor; final Color? surfaceTintColor; final bool forceElevated; final Color? backgroundColor; /// If backgroundGradient is non null, backgroundColor will be ignored final LinearGradient? backgroundGradient; final Color? foregroundColor; final IconThemeData? iconTheme; final IconThemeData? actionsIconTheme; final bool primary; final bool? centerTitle; final bool excludeHeaderSemantics; final double? titleSpacing; final double? expandedHeight; final double? collapsedHeight; final bool floating; final bool pinned; final ShapeBorder? shape; final double toolbarHeight; final double? leadingWidth; final TextStyle? toolbarTextStyle; final TextStyle? titleTextStyle; final SystemUiOverlayStyle? systemOverlayStyle; final bool forceMaterialTransparency; final Clip? clipBehavior; final bool snap; final bool stretch; final double stretchTriggerOffset; final AsyncCallback? onStretchTrigger; final CustomClipper? appBarClipper; @override State createState() => _DynamicSliverAppBarState(); } class _DynamicSliverAppBarState extends State { final GlobalKey _childKey = GlobalKey(); // As long as the height is 0 instead of the sliver app bar a sliver to box adapter will be used // to calculate dynamically the size for the sliver app bar double _height = 0; @override void initState() { super.initState(); _updateHeight(); } void _updateHeight() { // Gets the new height and updates the sliver app bar. Needs to be called after the last frame has been rebuild // otherwise this will throw an error WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (_childKey.currentContext == null) return; setState(() { _height = (_childKey.currentContext!.findRenderObject()! as RenderBox) .size .height; }); }); } @override void didChangeDependencies() { _height = 0; _updateHeight(); super.didChangeDependencies(); } @override Widget build(BuildContext context) { //Needed to lay out the flexibleSpace the first time, so we can calculate its intrinsic height if (_height == 0) { return SliverToBoxAdapter( child: SizedBox( key: _childKey, child: widget.flexibleSpace ?? SizedBox(height: kToolbarHeight), ), ); } MediaQuery.orientationOf(context); return SliverAppBar( leading: widget.leading, automaticallyImplyLeading: widget.automaticallyImplyLeading, title: widget.title, actions: widget.actions, bottom: widget.bottom, elevation: widget.elevation, scrolledUnderElevation: widget.scrolledUnderElevation, shadowColor: widget.shadowColor, surfaceTintColor: widget.surfaceTintColor, forceElevated: widget.forceElevated, backgroundColor: widget.backgroundColor, foregroundColor: widget.foregroundColor, iconTheme: widget.iconTheme, actionsIconTheme: widget.actionsIconTheme, primary: widget.primary, centerTitle: widget.centerTitle, excludeHeaderSemantics: widget.excludeHeaderSemantics, titleSpacing: widget.titleSpacing, collapsedHeight: widget.collapsedHeight, floating: widget.floating, pinned: widget.pinned, snap: widget.snap, stretch: widget.stretch, stretchTriggerOffset: widget.stretchTriggerOffset, onStretchTrigger: widget.onStretchTrigger, shape: widget.shape, toolbarHeight: widget.toolbarHeight, expandedHeight: _height + (widget.hasTabBar ? 48 : 0), leadingWidth: widget.leadingWidth, toolbarTextStyle: widget.toolbarTextStyle, titleTextStyle: widget.titleTextStyle, systemOverlayStyle: widget.systemOverlayStyle, forceMaterialTransparency: widget.forceMaterialTransparency, clipBehavior: widget.clipBehavior, flexibleSpace: FlexibleSpaceBar(background: widget.flexibleSpace), ); } }