Hopefully a simple question, I have SliverPersistentHeader inside a CustomScrollView, and it looks like this SliverPersistentHeader has some sort of shadow/elevation. Is there a way to remove it (I've outlined the shadow in the red box)?
See picture below:
This basic body is a scaffold with the SliverPersistentHeader coming from the _makeHeader call:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Consts.coMainBackground,
body: CustomScrollView(
slivers: <Widget>[
_sliverAppBar(),
_makeHeader(),
BlocBuilder<AllPersonsBloc, AllPersonsState>(
builder: (context, state) {
if (state is AllPersonsLoading) {
return _buildLoading();
} else if (state is AllPersonsLoaded) {
return _sliverList(context, state.persons);
} else if (state is AllPersonsError) {
return _buildErrorMessage(state.message);
} else {
return _buildErrorMessage('Unknown error!');
}
},
),
],
),
);
}
the Make Header function:
Widget _makeHeader() {
return SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
minHeight: 130.0,
maxHeight: 130.0,
child: Container(
color: Consts.coForestGreenBackground,
child: Container(
decoration: BoxDecoration(
color: Consts.coMainBackground,
borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15))
),
child: _cardHeader('People', Icons.search, 'Name')),
)
),
);
}
And finally the Delegate function:
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
#required this.minHeight,
#required this.maxHeight,
#required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
#override
double get minExtent => minHeight;
#override
double get maxExtent => math.max(maxHeight, minHeight);
#override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent)
{
return new SizedBox.expand(child: child);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
And card header (not actually a card)
Widget _cardHeader(String titleText, IconData inputIcon, String hint) {
return Container(
padding: EdgeInsets.symmetric(horizontal: standardEdgePadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(titleText, style: Consts.cardHeaderStyle,),
Container(
margin: EdgeInsets.only(top: 20),
height: 40,
decoration: BoxDecoration(
color: Consts.inputGreen,
borderRadius: BorderRadius.all(Radius.circular(10))
),
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Icon(
inputIcon,
color: Consts.inputGreenText,
size: Consts.cardInputIconSize,
),
),
Flexible(child: TextField(
decoration: new InputDecoration.collapsed(
hintText: hint,
hintStyle: Consts.cardInputStyle,
),
),)
],
),
)
],
),
);
}
SliverPersistentHeader alone does not have a shadow, therefore that elevation effect is not coming from your SliverPersistentHeader. It is not explicit from your code snippets, but I can see you have a _cardHeader('People', Icons.search, 'Name') method in your widget tree. I suspect it contains a Card widget inside the widget tree this method returns.
As seen in the Card widget Flutter documentation, Cards have a default non-zero elevation value, which might be casting the shadow in your case. See if there is any Card widget in your widget tree, and set its elevation parameter to zero.
So it turns out because, i have a container inside a container:
Widget _makeHeader() {
return SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
minHeight: 130.0,
maxHeight: 130.0,
child: Container(
color: Consts.coForestGreenBackground,
child: Container(
//There seemed to be some spacing between the search header and the rest of the list, this takes care of it
//transform: Matrix4.translationValues(0.0, 1.0, 0.0),
decoration: BoxDecoration(
color: Consts.coMainBackground,
borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15))
),
child: _cardHeader('People', Icons.search, 'Name')),
)
),
);
}
The way that the Header "meets" the SliverList there is an very minor gap (might be related to the emulator only). So the "coForestGreenBackground" colour in the parent container is showing through in that gap. There is no actual elevation/shadow.
If I uncomment the code, then the gap (aka elevation in my original question) vanishes.
I slept very poorly last night as I couldn't stop thinking about where I made a mistake. Thanks to #drogel for confirming there is no actual shadow/elevation on the SliverPersistentHeader.
Related
I am trying to create a profile header sliver that can animate.
If you consider above image, Section 1 is what we see in the fully expanded sliver, and Section 2 is what we want to see in pinned mode.
Now I would like transition to move the image - purple circle - to the side, shrink it slightly, and also move the name and the links.
I can achieve all of that but one thing: How to center them in the expanded view.
As I have to use transform to move widgets around, I cannot simply use a centring widget like column or center. And I didn't find a way to calculate the exact position to center the widget, as it needs the size of the widget, that I don't have.
Firstly I am using SliverPersistentHeaderDelegate and it provides shrinkOffset that will be used on linear interpolation(lerp method).
Then CompositedTransformTarget widget to follow the center widget.
On this example play with targetAnchor and followerAnchor and use t/shrinkOffset to maintain other animation.
class SFeb223 extends StatelessWidget {
const SFeb223({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
delegate: MySliverPersistentHeaderDelegate(),
pinned: true,
),
SliverToBoxAdapter(
child: SizedBox(
height: 1333,
),
)
],
),
);
}
}
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
final LayerLink layerLink = LayerLink();
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
double t = shrinkOffset / maxExtent;
return Material(
color: Colors.cyanAccent.withOpacity(.2),
child: Stack(
children: [
Align(
alignment:
Alignment.lerp(Alignment.center, Alignment.centerLeft, t)!,
child: CompositedTransformTarget(
link: layerLink,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: lerpDouble(100, kToolbarHeight - 10, t),
width: lerpDouble(100, kToolbarHeight - 10, t),
decoration: const ShapeDecoration(
shape: CircleBorder(),
color: Colors.deepPurple,
),
),
),
),
),
CompositedTransformFollower(
link: layerLink,
targetAnchor: Alignment.lerp(
Alignment.bottomCenter, Alignment.centerRight, t)!,
followerAnchor:
Alignment.lerp(Alignment.topCenter, Alignment.centerLeft, t)!,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Column(
children: [Text("Sheikh")],
),
),
),
),
],
),
);
}
#override
double get maxExtent => kToolbarHeight * 6;
#override
double get minExtent => kToolbarHeight;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
false;
}
I have a Flutter app with screens rendered conditionally with an array. Anyway, I need to have a popup screen like this :
If have stored all my "popup screens" in an array and rendered the main screen and the popup screen in a stack. I don't know if this is the best solution and I guess I will have performance issues.
Here is the PopupContainerclass, this Widget is rendered on every Popup Screen with the child passed as content :
class PopupContainer extends StatefulWidget {
final Widget? child;
const PopupContainer({
Key? key,
this.child,
}) : super(key: key);
#override
State<PopupContainer> createState() => _PopupContainerState();
}
class _PopupContainerState extends State<PopupContainer> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Consumer<ScreenManager>(
builder: (context, manager, child) => Stack(
alignment: Alignment.bottomCenter,
children: [
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(
decoration: BoxDecoration(color: Colors.white.withOpacity(0.0)),
),
),
Container(
height: height * 0.8,
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
boxShadow: [
BoxShadow(
blurRadius: 37,
spreadRadius: 0,
color: Color.fromRGBO(28, 48, 72, 0.24),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
alignment: Alignment.topRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
primary: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () => manager.closePopup(),
child: SvgPicture.asset('assets/close.svg'),
),
),
widget.child ?? const SizedBox.shrink(),
],
),
),
],
),
);
}
}
The consumer is used for handling the screens states :
enum ScreensName {
homeScreen,
favoriteProductsScreen,
archivedListsScreen,
recipesScreen,
}
enum PopupsName {
newProductPopup,
archivedListPopup,
editProductPopup,
newRecipePopup,
}
const screens = <ScreensName, Widget>{
ScreensName.homeScreen: HomeScreen(),
ScreensName.favoriteProductsScreen: FavoriteProductsScreen(),
ScreensName.archivedListsScreen: ArchivedListsScreen(),
ScreensName.recipesScreen: RecipesScreen(),
};
const popups = <PopupsName, Widget>{
PopupsName.newProductPopup: NewProductPopup(),
};
class ScreenManager extends ChangeNotifier {
static ScreensName screenName = ScreensName.homeScreen;
static PopupsName? popupName = PopupsName.newProductPopup;
get currentScreen => screens[screenName];
get currentPopup => (popups[popupName] ?? Container());
/// Open the given popup.
void openPopup(PopupsName newPopupName) {
popupName = newPopupName;
notifyListeners();
}
/// Closes the current popup.
void closePopup() {
popupName = null;
notifyListeners();
}
/// Change the screen.
void setScreen(ScreensName newScreenName) {
screenName = newScreenName;
notifyListeners();
}
}
And finally, the main component build method (I also have some theme styling but useless here) :
Widget build(BuildContext context) {
DatabaseHelper.initDb();
return Consumer<ScreenManager>(
builder: (context, screenManager, child) => Material(
child: MaterialApp(
title: _title,
theme: _customTheme(),
home: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
screenManager.currentScreen,
screenManager.currentPopup,
],
),
),
),
);
}
PS : I am a web developer so I know the main programming principles but Dart and mobile dev is brand new for me. Also, I could share my code with you, however, this project is splitted into files and it would take too much space in the post. Ask if you need it !
Maybe an easier solution would be to use the showDialog function where you need to trigger the popup. Check out the docs https://api.flutter.dev/flutter/material/showDialog.html
showDialog(context: context, builder: (context) => AlertDialog(title: Text('Title'), content: Text('Here content'),));
I'm trying to implement a layout, where the Sliver App Bar has rounded bottom corners when expanded, but when it is collapsed I do not want those rounded corners.
Actual Behaviour:
enter image description here
Expected Behaviour:
Here's my SliverAppBar code:
`SliverAppBar(
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Color(0xFFE0E64B),
),
backgroundColor: Color(0xFFE0E64B),
expandedHeight: 300.0,
floating: false,
pinned: true,
collapsedHeight: 60.0,
onStretchTrigger: () async {
setState(() {});
},
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'Pokedex',
style: TextStyle(
color: Colors.white,
),
),
Text(
'#025',
style: TextStyle(
color: Colors.white,
),
),
],
),
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: Container(
decoration: const BoxDecoration(
color: Color(0xFFE0E64B),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(50.0),
bottomRight: Radius.circular(50.0),
),
),
child: Hero(
tag: 'pokemon_container$index',
child: Column(
children: [
const SizedBox(
height: 120.0,
),
Expanded(
child: ClipRRect(
child: Image.network(
imageUrl,
fit: BoxFit.scaleDown,
),
),
),
const SizedBox(
height: 30.0,
),
],
),
),
),
),
),`
shape: ContinuousRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30))),
Here is your code. Put it inside sliverAppBar
NestedScrollView / SliverAppBar solution
This is definitely achievable. SliverAppBar does support what we need, it has support for rounded borders, the shadow effect and changing sizes. For handling the border requirement we can use a RoundedRectangleBorder.
Although for getting a smooth transition for the border change, we need to update the values frequently, when changing the size of the SliverAppBar.
Example code
Do note that the package flutter_riverpod (version 1.0.3) is used for state management in this example.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class RoundedSliverExampleScreen extends StatelessWidget {
const RoundedSliverExampleScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
ExpandingAppBar(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Flexible is important for the children widgets added here.
Flexible(child: Container(color: Colors.yellow, width: 50, height: 50,))
],
)
];
},
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text("Hello!")
],
),
)
);
}
}
/// An SliverAppBar widget with alternating rounded border depending on the
/// expandedHeight.
///
/// Provides easy support for adding children widgets in the
/// expanded area as if it was a Column, although children widgets should be
/// wrapped in a Flexible widget.
class ExpandingAppBar extends ConsumerWidget {
const ExpandingAppBar({
Key? key,
this.children = const <Widget>[],
this.mainAxisAlignment = MainAxisAlignment.start
}) : super(key: key);
final List<Widget> children;
final MainAxisAlignment mainAxisAlignment;
#override
Widget build(BuildContext context, WidgetRef ref) {
RoundedHeaderState state = ref.watch(roundedHeaderProvider);
return SliverAppBar(
expandedHeight: state.highestHeight,
pinned: true,
primary: true,
forceElevated: true,
title: const Text('Pokèdex'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(state.radius)),
),
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// We update the state here.
ref.read(roundedHeaderProvider.notifier).updateHeight(constraints.maxHeight);
return Opacity(
opacity: state.scrollFraction,
child: Padding(
padding: EdgeInsets.only(top: state.smallestHeight),
child: Column(mainAxisAlignment: mainAxisAlignment, children: children),
),
);
},
),
);
}
}
#immutable
class RoundedHeaderState {
final double highestHeight = 256;
final double smallestHeight = kToolbarHeight + 24;
final double currentHeight;
final double contentOpacity = 1;
const RoundedHeaderState({this.currentHeight = 256});
double get scrollFraction => min(max((currentHeight - smallestHeight) / (highestHeight - smallestHeight), 0), 1);
double get radius => 64 * scrollFraction;
}
class RoundedHeaderNotifier extends StateNotifier<RoundedHeaderState> {
RoundedHeaderNotifier(): super(const RoundedHeaderState());
updateHeight(double currentHeight) {
final newState = RoundedHeaderState(currentHeight: currentHeight);
// Check that the new state is not equal to the next (prevents rebuild loop)
if(state.currentHeight != newState.currentHeight) {
// Setting state triggers an rebuild, the PostFrameCallback let Flutter
// postpone the upcoming rebuild at a later time.
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
state = newState;
});
}
}
}
final roundedHeaderProvider = StateNotifierProvider<RoundedHeaderNotifier, RoundedHeaderState>((ref) {
return RoundedHeaderNotifier();
});
// Pay attention to the ProviderScope wrapping the MaterialApp. Riverpod requires this.
void main() => runApp(
const ProviderScope(
child: MaterialApp(home: RoundedSliverExampleScreen())
)
);
Result - Gif of the SliverAppBar's transition.
I'm developing a parallax-style header/background block in my flutter application, which scrolls upwards at around 1/3 the speed of the foreground content. All parts in the foreground are within the same customScrollView and the background header is in a positioned container at the top of the stack.
I'm using a listener on the customscrollview to update a y-offset integer, and then using that integer to update the top position on the element inside my stack.
While this works as expected, the issue I'm facing is a large amount of repainting takes place on scroll, which in the future may impact performance. I'm sure there may be a more efficient way to achieve this - such as placing the entire background in a separate child widget and passing the controller down to it from the parent widget - however I am struggling to find any information on doing so, or if this is the correct approach.
Can someone point me in the right direction for refactoring this in such a way as to disconnect the scrolling background from the foreground, so that the foreground doesn't repaint constantly?
class ScrollingWidgetList extends StatefulWidget {
ScrollingWidgetList();
#override
State<StatefulWidget> createState() {
return _ScrollingWidgetList();
}
}
class _ScrollingWidgetList extends State<ScrollingWidgetList> {
ScrollController _controller;
double _offsetY = 0.0;
_scrollListener() {
setState(() {
_offsetY = _controller.offset;
});
}
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: -(_offsetY / 3),
child: ConstrainedBox(
constraints: new BoxConstraints(
maxHeight: 300.0,
minHeight: MediaQuery.of(context).size.width * 0.35),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Theme.of(context).primaryColorDark,
Colors.blueGrey[900].withOpacity(0.8)
],
)),
height: MediaQuery.of(context).size.width * 0.35)),
width: MediaQuery.of(context).size.width,
),
CustomScrollView(controller: _controller, slivers: [
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: ListTile(
title: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Text('Header text',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
subtitle: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Subtitle text',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white)),
),
))
])),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return FakeItem(
executing: false,
delay: index.isOdd,
complete: false,
cancelled: false);
},
childCount: 30,
)),
])
],
);
}
}
A great solution was added by #pskink in the comments, however they seemed to have removed it. For anyone searching for an elegant solution, this is the basics of what was settled on.
You can see in the below code there is two layouts that are being handled by CustomMultiChildLayout. Hopefully this helps anyone searching for a similar solution
class ScrollList extends StatelessWidget {
final ScrollController _controller = ScrollController();
#override
Widget build(BuildContext context) {
return CustomMultiChildLayout(
delegate: ScrollingChildComponentDelegate(_controller),
children: <Widget>[
// background element layout
LayoutId(
id: 'background',
child: DecoratedBox(
decoration: BoxDecoration(
// box decoration
),
),
),
// foreground element layout
LayoutId(
id: 'scrollview',
child: CustomScrollView(
controller: _controller,
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: ListTile(
title: Text('TitleText'),
),
subtitle: Text('SubtitleText'),
)),
),
SliverList(
delegate: SliverChildBuilderDelegate(itemBuilder,
childCount: 100),
),
],
)),
],
);
}
}
// itembuilder for child components
Widget itemBuilder(BuildContext context, int index) {
return Card(
margin: EdgeInsets.all(6),
child: ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))),
child: Container(
// child element content
)));
}
// controller for the animation
class ScrollingChildComponentDelegate extends MultiChildLayoutDelegate {
final ScrollController _controller;
ScrollingChildComponentDelegate(this._controller) : super(relayout: _controller);
#override
void performLayout(Size size) {
positionChild('background', Offset(0, -_controller.offset / 3));
layoutChild('background',
BoxConstraints.tightFor(width: size.width, height: size.height * 0.2));
positionChild('scrollview', Offset.zero);
layoutChild('scrollview', BoxConstraints.tight(size));
}
#override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => true;
}
Gif of said error
I have a SliverPersistentHeader, that as a sliver does what it is supposed to do. However the content in the sliver causes renderflex errors.
How do i fix it, so that the content inside the sliver resizes with the sliver? I choose the SliverPersistentHeader to create a stack of headers, maybe there is another widget that would be more fitting?
The slivers are built in a CustomScrollView as objects in a list of slivers.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
import 'package:weather/weather.dart';[enter image description here][1]
import 'dart:math' as math;
class TracksPage extends StatelessWidget{
TracksPage();
List tracks = List();
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
),
body: CollapsingList()
);
}}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
#required this.minHeight,
#required this.maxHeight,
#required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
#override
double get minExtent => minHeight;
#override
double get maxExtent => math.max(maxHeight, minHeight);
#override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent)
{
return new SizedBox.expand(child: child);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
class CollapsingList extends StatelessWidget {
SliverPersistentHeader makeHeader(List<int> sums, int tracks, DateTime day) {
return SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
minHeight: 0.0,
maxHeight: 80.0,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[400]),
color: Colors.white
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.grey,
child: Text(tracks.toString(), style: TextStyle(color: Colors.black),),),
title: Text(" ${day.day}/${day.month}/${day.year}"),
subtitle: Row(children: <Widget>[
statCircle(Image.asset("assets/images/steps.png", height: 20, fit: BoxFit.cover,), [Color(0xFFC312E4), Color(0xFF841779)], sums[0].toString(), 0),
Padding(padding: EdgeInsets.only(right: 30),),
statCircle(Icon(Icons.timer, size: 15,), [Color(0xFF73E412), Color(0xFF5B9B1A)], "", 5),
Padding(padding: EdgeInsets.only(right: 30),),
statCircle(Icon(Icons.pin_drop, size: 15,), [Color(0xFF12D6E4), Color(0xFF118470)], sums[1].toString(), 5),]),
)
)
),
);
}
Column statCircle(Widget child, List<Color> colors, String info, double padding){
return Column(children: <Widget>[
Container(
child: child,
padding: EdgeInsets.all(7),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: colors,
stops: [0.3, 0.6],
begin: Alignment.topCenter,
end: Alignment.bottomCenter
)
),
),
Padding(padding: EdgeInsets.only(bottom: padding),),
Text(info)
],);
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance.collection("collectionName").where("fieldName", isEqualTo:fieldVariable).snapshots(),
builder: (context, snapshot){
if(snapshot.hasData){
List dates = extractAndSort(snapshot);
return CustomScrollView(
slivers: makeSlivers(dates, context)
);
}
else return Container();
}
);
}
makeSlivers(List dates, BuildContext context){
List<Widget> slivers = new List<Widget>();
List totals = summarize(Globals.tracks);
int length;
if(Globals.tracks != null){
length = Globals.tracks.length;
} else length = 0;
slivers.add( makeCirclesHeader(totals[0], length, totals[1], context));
slivers.add(SliverPadding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
sliver: SliverToBoxAdapter(
child: Container(
height: 20,
color: Colors.white)
)
));
if(dates != null)
{dates.forEach((date) {
List tracks = Globals.tracks.where((track) => track.trackCreated.toDate().day == date.day).toList();
slivers.add(makeHeader(summarize(tracks), tracks.length, date));
slivers.add(sliverListDay(tracks));
});}
return slivers;
}
You need to wrap it like this: _SliverAppBarDelegate -> Container -> ClipRect -> OverflowBox -> ListTile
delegate: _SliverAppBarDelegate(
minHeight: 0.0,
maxHeight: 80.0,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[400]),
color: Colors.white
),
child: ClipRect(
child: OverflowBox(
alignment: Alignment.topLeft,
maxHeight: 80.0,
child: ListTile(
leading: CircleAvatar(
...