Created
March 30, 2021 11:55
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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