Skip to content

Instantly share code, notes, and snippets.

@leeprobert
Created March 30, 2021 11:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leeprobert/ae10bb2361dbea041a14bf612d67dfe7 to your computer and use it in GitHub Desktop.
Save leeprobert/ae10bb2361dbea041a14bf612d67dfe7 to your computer and use it in GitHub Desktop.
A custom SliverAppBar with flexible spaces for the expanded and collapsed state. Includes an option for showing a multiline title with a feature image in the expanded state. When collapsed the two spaces will fade between each other with a single line title for the collapsed space.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'RootAppBar.dart';
import 'dart:math' as math;
class RootSliverAppBar extends StatefulWidget {
RootSliverAppBar({
Key key,
this.title,
this.actions,
this.isModal,
this.backgroundImageUrl,
this.featureImageUrl,
this.backgroundGradient,
}) : super(key: key);
final String title;
final List<Widget> actions;
final String backgroundImageUrl;
final String featureImageUrl;
final LinearGradient backgroundGradient;
final bool isModal;
@override
_RootSliverAppBarState createState() => _RootSliverAppBarState();
}
class _RootSliverAppBarState extends State<RootSliverAppBar> {
double _titleOpacity = 0.0;
double _spaceOpacity = 1.0;
@override
Widget build(BuildContext context) {
List<Widget> getActions() {
if (widget.actions != null) {
return widget.actions;
} else if (widget.isModal) {
return null;
} else {
return <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () {
RootScaffold.openDrawer(context);
},
),
];
}
}
List<Widget> getBackgroundStackWidgets() {
List<Widget> stackWidgets = [];
// if there's a gradient use it as a background
if (widget.backgroundGradient != null) {
stackWidgets.add(
DecoratedBox(
decoration: BoxDecoration(
gradient: widget.backgroundGradient,
),
),
);
}
// if there's a background image we can add this as a layer too
if (widget.backgroundImageUrl != null) {
stackWidgets.add(
Image.network(
widget.backgroundImageUrl,
fit: BoxFit.cover,
),
);
}
if (widget.title != null) {
// Add the dark grad for legibility
stackWidgets.add(
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment(0.0, 0.5),
end: Alignment(0.0, 0.0),
colors: <Color>[
Color(0x60000000),
Color(0x00000000),
],
),
),
),
);
}
return stackWidgets;
}
Widget getTitleWidget({bool isCollapsed = false}) {
if (isCollapsed) {
return Text(
// widget.title,
"This is a really, really, long title that should be wrapped around on a maximum of three lines. Not sure when we'll need to show a Title this long, but you never know!",
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}
final titleText = Text(
"This is a really, really, long title that should be wrapped around on a maximum of three lines. Not sure when we'll need to show a Title this long, but you never know!",
maxLines: 3,
overflow: TextOverflow.ellipsis,
);
return widget.featureImageUrl != null
? Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
flex: 2,
child: titleText,
),
SizedBox(
width: 10,
),
Expanded(
flex: 1,
child: CachedNetworkImage(
imageUrl: widget.featureImageUrl,
fit: BoxFit.contain,
),
),
],
)
: titleText;
}
return SliverAppBar(
stretch: true,
elevation: 0.0,
collapsedHeight: 60.0,
expandedHeight: 200.0,
pinned: true,
backgroundColor: Colors.grey, // TODO: theme color
actions: getActions(),
centerTitle: false,
flexibleSpace: LayoutBuilder(
builder: (context, c) {
final settings = context
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final deltaExtent = settings.maxExtent - settings.minExtent;
final t = (1.0 -
(settings.currentExtent - settings.minExtent) / deltaExtent)
.clamp(0.0, 1.0) as double;
final fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
const fadeEnd = 1.0;
_titleOpacity = Interval(fadeStart, fadeEnd).transform(t);
_spaceOpacity = 1.0 - _titleOpacity.abs();
return Stack(
children: [
Opacity(
opacity: _spaceOpacity,
child: FlexibleSpaceBar(
stretchModes: const <StretchMode>[
StretchMode.zoomBackground,
StretchMode.blurBackground,
StretchMode.fadeTitle,
],
centerTitle: false,
collapseMode: CollapseMode.parallax,
title: Container(
padding: EdgeInsets.fromLTRB(0, 0, 20.0, 0),
child: getTitleWidget(),
),
titlePadding: EdgeInsetsDirectional.fromSTEB(20, 0, 20, 0),
background: Stack(
fit: StackFit.expand,
children: getBackgroundStackWidgets(),
),
),
),
Opacity(
opacity: _titleOpacity,
child: FlexibleSpaceBar(
centerTitle: false,
title: getTitleWidget(isCollapsed: true),
titlePadding:
EdgeInsetsDirectional.fromSTEB(20, 0, 20.0, 20.0),
),
),
],
);
},
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment