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(
...
Related
I want that as I move my slider button towards right, the opacity of text decreases and arrow icon rotates exactly oppposite, i.e. it strts rotating and at last last it should point backwards. I want to use opacity and Transform.rotate widgets, but how do I keep updating the value of dx ,so I can divide it with total width of container and use the fraction for calculation.
If there is another way, please do tell me.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:passenger_flutter_app/utils/colors.dart';
import 'package:passenger_flutter_app/widgets/custom_sliding_button.dart';
class CommonSwipeButton extends StatelessWidget {
final String? buttonText1;
final String buttonText2;
final VoidCallback buttonCallBack2;
final bool isInfo;
final VoidCallback? buttonCallBack1;
final Widget itemWidget;
CommonSwipeButton(
{this.buttonCallBack1,
required this.buttonCallBack2,
this.isInfo = false,
this.buttonText1,
required this.buttonText2,
required this.itemWidget});
#override
Widget build(BuildContext context) {
return Container(
child: Column(
//crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0, top: 16), child: itemWidget),
Padding(
padding:
const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: 44,
child: CustomSlidingButton(
//text: buttonText2,
),
),
),
)
],
),
);
}
}
/*
class SwipeButton extends StatefulWidget {
final ValueChanged<double>? valueChanged;
final String? text;
final Function? callBack;
SwipeButton({this.valueChanged, this.text, this.callBack});
#override
SwipeButtonState createState() {
return new SwipeButtonState();
}
}
class SwipeButtonState extends State<SwipeButton> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
GlobalKey swipeKey = GlobalKey();
ValueNotifier<double> x=ValueNotifier<double>(0);
ValueNotifier<bool> isVisible = ValueNotifier<bool>(true);
#override
void initState() {
valueListener.addListener(notifyParent);
super.initState();
}
void notifyParent() {
if (widget.valueChanged != null) {
widget.valueChanged!(valueListener.value);
}
}
void getPos(double totalSize) {
RenderBox box = swipeKey.currentContext?.findRenderObject() as RenderBox;
Offset position = box.localToGlobal(Offset.zero); //this is global position
x.value = position.dx;
print(x);
if(x.value>355) {
print("Reached");
isVisible.value=false;
}
}
#override
Widget build(BuildContext context) {
return Container(
color: colorPrimary,
height: 40.0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
"${widget.text}",
style: TextStyle(
color: Colors.white,
fontSize: 17,
),
),
),
),
Builder(
builder: (context) {
final handle = GestureDetector(
onHorizontalDragUpdate: (details) {
valueListener.value = (valueListener.value +
details.delta.dx / context.size!.width)
.clamp(.0, 1.0);
getPos(context.size!.width-5);
print(context.size?.width);
},
child: ValueListenableBuilder(
valueListenable: isVisible,
builder: (BuildContext context, bool val, Widget? child) {
return Container(
key: swipeKey,
height: 25.0,
width: 25.0,
color: val ? Colors.white : colorPrimary,
child: Center(
child: ValueListenableBuilder(
valueListenable: x,
builder: (BuildContext context, double d, Widget? child) {
return Transform.rotate(
angle: -pi*(d/350),
child: Icon(
Icons.arrow_forward,
color: Colors.orange,
size: 12,
),
);
},
),
),
);
},
),
);
return AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return Align(
alignment: Alignment(valueListener.value * 2 - 1, 0),
child: child,
);
},
child: handle,
);
},
),
],
),
);
}
}*/
You can use Slider widget from Flutter framework and update a local variable in the onChange function:
Slider(
value: _currentSliderValue,
max: 100, //or any max value you need
onChanged: (double value) {
setState(() {
_value = value;
});
},
);
And the _value variable you will use in Opacity and Transform widgets.
Here is my code :
import 'package:all_in_one/cooking/pages/recipe/header/search_bar_header.dart';
import 'package:all_in_one/cooking/pages/recipe/header/welcome_header.dart';
import 'package:flutter/material.dart';
class MyRecipePage extends StatefulWidget {
final String title;
MyRecipePage({Key? key, required this.title}) : super(key: key);
#override
_MyRecipePageState createState() => _MyRecipePageState();
}
class _MyRecipePageState extends State<MyRecipePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
WelcomeHeader(),
SearchBarHeader(),
SliverGrid.count(),
],
) ,
);
}
}
class WelcomeHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return
SliverPersistentHeader(
floating: true,
delegate: SliverAppBarDelegate(
minHeight: 0,
maxHeight: 100,
child: Container(
color: Colors.white,
child: _MyWelcomingHeader(),
),
),
);
}
}
class _MyWelcomingHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Flexible(
child: CircleAvatar(
radius: 57,
backgroundColor: Colors.grey.shade50,
child: Image.asset("assets/emoji-food.jpg"),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Enjoy the recipes',
style: TextStyle(
color: Colors.black,
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
}
class SearchBarHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SliverPersistentHeader(
pinned: true,
delegate: SliverAppBarDelegate(
minHeight: 50,
maxHeight: 50,
child: Container(
color: Colors.white,
child: _MySearchBar(),
),
),
);
}
}
class _MySearchBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(width: 10),
const Icon(Icons.search, color: Colors.grey, size: 30),
const SizedBox(width: 5),
Text("Search product",
style: TextStyle(
color: Colors.grey.shade500, fontSize: 12, fontWeight: FontWeight.w200))
],
);
}
}
The code of the silver bar Delegate is from this stackoverflow post
import 'package:flutter/material.dart';
import 'dart:math' as math;
class SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final double minHeight;
final double maxHeight;
final Widget child;
SliverAppBarDelegate({
required this.minHeight,
required this.maxHeight,
required this.child,
});
#override
double get minExtent => minHeight;
#override
double get maxExtent => math.max(maxHeight, minHeight);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
#override
bool shouldRebuild(SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
My goal is to have 2 SliverPersistentHeader, one pinned and one floating.
The one floating (the 1st one) should resize the text and the image while scrolling..
In the following screenshot, we can see that the 2nd SliverPersistentHeader is overlapping the 1st one.
How can I do to make the Text resize itself. I try to use Flexible like I did for the CircleAvatar but I can't succeed :/
Thanks
I found a solution using the opacity, so my WelcomeHeader becomes :
import 'package:flutter/material.dart';
import 'dart:math' as math;
class WelcomeHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SliverAppBar(
backgroundColor: Colors.white,
pinned: false,
floating: false,
snap: false,
expandedHeight: 120,
flexibleSpace: _MyWelcomingHeader()
);
}
}
class _MyWelcomingHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return 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);
final fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
const fadeEnd = 1.0;
final opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Opacity(
opacity: opacity,
child: Column(
children: [
Flexible(
child: CircleAvatar(
radius: 57,
backgroundColor: Colors.grey.shade50,
child: Image.asset("assets/emoji-food.jpg"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Enjoy the recipes !',
style: TextStyle(
color: Colors.black,
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
});
}
}
I'm using Custom flutter icons which is generated from fluttericons.com .But here the problem is my icons are not appeared as it. It shows rectangular box with strikeout symbol in preview like this. (Refer the image below )
Here is my code snippet for my Custom icon button widget.
import 'package:flutter/material.dart';
import 'package:show_up_animation/show_up_animation.dart';
import 'social_media_icons.dart';
import 'constants.dart';
class AnimativeIcons extends StatefulWidget {
#override
_AnimativeIconsState createState() => _AnimativeIconsState();
}
class _AnimativeIconsState extends State<AnimativeIcons> {
#override
Widget build(BuildContext context) {
return ResponsiveWidget(
largeScreen: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceBetween,
spacing: 20.0,
children: <Widget>[
AnimatedButton(
hoverIconData: SocialMediaIcons.github,
delay: 200,
url: "https://www.google.com"),
AnimatedButton(
hoverIconData: SocialMediaIcons.linkedin,
delay: 400,
url: "https://www.google.com"),
AnimatedButton(
hoverIconData: SocialMediaIcons.hackerrank,
delay: 600,
url: "https://www.google.com"),
AnimatedButton(
hoverIconData: SocialMediaIcons.twitter,
delay: 800,
url: "https://www.google.com"),
AnimatedButton(
hoverIconData: SocialMediaIcons.instagram,
delay: 1000,
url: "http://www.google.com"),
AnimatedButton(
hoverIconData: SocialMediaIcons.gmail,
delay: 1200,
url: "www.google.com"),
],
),
);
}
}
class AnimatedButton extends StatefulWidget {
final Color animationColor;
final IconData hoverIconData;
final int delay;
final String url;
AnimatedButton(
{this.animationColor, this.hoverIconData, this.delay, this.url});
#override
_AnimatedButtonState createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with SingleTickerProviderStateMixin {
_launchURL(String goToUrl) async {
String url = goToUrl;
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
Color iconColor;
#override
void initState() {
super.initState();
iconColor = ButtonColor.hoverColor;
}
#override
Widget build(BuildContext context) {
return ShowUpAnimation(
animationDuration: Duration(seconds: 1),
delayStart: Duration(milliseconds: widget.delay),
curve: Curves.easeIn,
direction: Direction.vertical,
offset: 1.0,
child: InkWell(
onTap: () {
_launchURL(widget.url);
print('pressed');
},
onHover: (value) {
if (value) {
setState(() {
iconColor = widget.animationColor;
});
} else {
setState(() {
iconColor = ButtonColor.hoverColor;
});
}
},
child: Icon(widget.hoverIconData, size: 50, color: iconColor),
),
);
}
}
If want to make a hover icon button.I tried with IconButton and failed ..Is there any way to do it..or
please any one help me to get out from this.
As i know there is no hover action on mobile, so i change the splash color of the icon button.
I've made a CustomIcon button class for my own, hope this might help you:
class IconButton extends StatelessWidget {
String text = 'demoText';
Color hoverColor;
String icon ='';
GestureTapCallback onTap;
IconButton(
{Key key,
this.text,
this.icon,
this.onTap,
this.hoverColor = Colors.grey})
: assert(text != null),
assert(icon != null),
super(key: key);
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Container(
height: height * .155,
width: width * .44,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(width: .2, color: Colors.green),
color: Color(0xffFFFFFF),
boxShadow: [
BoxShadow(
color: Colors.grey[350], blurRadius: 3, offset: Offset(2.5, 4))
]),
child: Material(
clipBehavior: Clip.antiAlias,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
// <------------------------- Inner Material
type: MaterialType.transparency,
elevation: 6.0,
color: Colors.transparent,
shadowColor: Colors.grey[50],
child: InkWell(
//<------------------------- InkWell
splashColor: hoverColor,
onTap: onTap,
child: Container(
padding: EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
Container(
height: height * .07,
width: width * .15,
child: CachedNetworkImage(
imageUrl: icon,
progressIndicatorBuilder:
(context, url, downloadProgress) =>
CircularProgressIndicator(
value: downloadProgress.progress),
errorWidget: (context, url, error) =>
Icon(Icons.error),
),
),//please add as you
need:Image.asset,SvgPicture.asset etc
SizedBox(
height: height * .009,
),
Text(
text,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
color: Color(0xff434343)),
)
],
)
],
),
),
),
),
);
}
}
You can modify as your need. ex call:
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
hoverColor: Colors.greenAccent,//Change hover color as you need
text: "Google",
icon:"any image url",
onTap: () {},
),
],
),
),
);
}
}
Let me know if it's help you, or you find difficulties.
Package use to display network images : https://pub.dev/packages/cached_network_image
I'm trying to implement a solution where a row (containing both a TextField and a Text) in ListView.Builder is automatically for every record retrieved from a webserver.
However when I want to start typing in such a TextField the keyboard appears and immediatly disappears again.
This is the code of my screen.
class GameScreen extends StatelessWidget {
static const RouteName = "/GameScreen";
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
const horizontalMargin = 20.0;
return Scaffold(
appBar: getAppBar(),
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
Background(),
Column(
children: <Widget>[
Header("Starting letter: B"),
Expanded(
child: ListBlocProvider(
listWidget: GameCategoriesList(),
itemsService: CategoriesService(),
margin: EdgeInsets.only(
left: horizontalMargin,
bottom: 10,
right: horizontalMargin,
),
),
),
SizedBox(
height: 20,
),
SizedBox(
width: size.width - 40,
height: 60,
child: Container(
height: 60,
child: TextButtonWidget(
() {
// Navigator.of(context).pushNamed(GameScreen.RouteName);
},
"Stop game",
),
),
),
SizedBox(
height: 20,
)
],
),
],
),
);
}
}
This is the code of my ListBlocProvider:
class ListBlocProvider extends StatelessWidget {
final ListWidget listWidget;
final ItemsService itemsService;
final bool useColor;
final bool usePaddingTop;
final double height;
final EdgeInsets margin;
const ListBlocProvider({
#required this.listWidget,
#required this.itemsService,
this.useColor = true,
this.usePaddingTop = true,
this.height = 200,
this.margin,
});
#override
Widget build(BuildContext context) {
const horizontalMargin = 20.0;
return BlocProvider(
create: (context) => ItemsBloc(itemsService: itemsService)..add(ItemsFetched()),
child: Container(
padding: usePaddingTop ? EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: this.useColor ? Color.fromRGBO(10, 50, 75, 0.9) : null,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
margin: this.margin,
height: this.height,
child: this.listWidget,
),
);
}
}
This is the code of my List:
class GameCategoriesList extends ListWidget {
#override
_GameCategoriesListState createState() => _GameCategoriesListState();
}
class _GameCategoriesListState extends State<GameCategoriesList> {
#override
Widget build(BuildContext context) {
return BlocBuilder<ItemsBloc, ItemsState>(
builder: (context, state) {
if (state is ItemsFailure) {
return Center(
child: Text('failed to fetch categories'),
);
}
if (state is ItemsSuccess) {
if (state.items.isEmpty) {
return Center(
child: Text('no categories found.'),
);
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
var textEditingController = TextEditingController();
return GameCategoryItemWidget(
key: UniqueKey(),
categoryModel: state.items[index],
textEditingController: textEditingController,
);
},
itemCount: state.items.length,
);
}
return Center(
child: LoadingIndicator(),
);
},
);
}
}
And this is the code where the both the TextField and the Text are build:
class GameCategoryItemWidget extends StatefulWidget {
final CategoryModel categoryModel;
final TextEditingController textEditingController;
const GameCategoryItemWidget({Key key, this.categoryModel, this.textEditingController}) :
super(key: key);
#override
_GameCategoryItemWidgetState createState() => _GameCategoryItemWidgetState();
}
class _GameCategoryItemWidgetState extends State<GameCategoryItemWidget> {
var formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: this.formKey,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 20, bottom: 10),
child: Text(
this.widget.categoryModel.name,
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
Container(
color: Colors.white,
child: InputField(
InputDecoration(labelText: this.widget.categoryModel.name),
this.widget.textEditingController,
false,
),
),
],
),
),
);
}
#override
void dispose() {
this.widget.textEditingController.dispose();
super.dispose();
}
}
The InputField is a custom widget to hide the switch between a Material and a Cupertino version of the TextField.
I've already tried to remove the Key from the custom TextField widget. The funny part is that the input is actually working, however it can't determine for which of the TextFields in the ListView the input is determined so it adds the input to all of them. I've also tried to swap things around with making Stateless widgets Statefull, but that didn't help either.
The entire build is based upon: https://bloclibrary.dev/#/flutterinfinitelisttutorial.
Hoping you guys can help me.
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.