Closed aaqibismail closed 3 years ago
Option 1: put SliverAppBar into CustomScrollView. Option 2: try extended_nested_scroll_view, maybe work.
Option 1: put SliverAppBar into CustomScrollView. Option 2: try extended_nested_scroll_view, maybe work.
Option 1 won't help too much in my case because then I don't need the NestedScrollView anyways. Option 2 also didn't change this behavior, both the regular and extended NestedScrollViews have the same issue.
add NestedScrollView Example, maybe it can help you.
Thanks for the example. I tried adapting the example for my use-case and while it technically worked, but from a UX standpoint the transition between the SliverAppbars wasn't very fluid or smooth; it felt a little distracting. I tried using an animated switcher to switch the height of the 2nd SliverAppbar when it was supposed to be pinned but it didn't help much. Is there any way to use a this package with a NestedScrollView without 2 SliverAppbars? Here's a screen recording of what I am referring to. Thank you for the help.
import 'package:flutter/material.dart';
import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
import 'mock_data.dart';
class NestedScrollViewTest extends StatelessWidget {
const NestedScrollViewTest({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter sticky and expandable list',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ExampleNestedScrollView(),
);
}
}
class ExampleNestedScrollView extends StatefulWidget {
const ExampleNestedScrollView({Key key}) : super(key: key);
@override
_ExampleNestedScrollViewState createState() =>
_ExampleNestedScrollViewState();
}
class _ExampleNestedScrollViewState extends State<ExampleNestedScrollView>
with TickerProviderStateMixin {
List<ExampleSection> sectionList = MockData.getExampleSections();
final GlobalKey<NestedScrollViewState> nestedScrollKey = GlobalKey();
final double _expandedHeight = 200;
bool _isPinnedTitleShown = false;
@override
void initState() {
super.initState();
var headerContentHeight = _expandedHeight;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
outerController.addListener(() {
var pinned = outerController.offset >= headerContentHeight;
if (_isPinnedTitleShown != pinned) {
setState(() {
_isPinnedTitleShown = pinned;
});
}
print("outerController position: $outerController $kToolbarHeight");
});
});
}
ScrollController get outerController {
return nestedScrollKey.currentState.outerController;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
key: nestedScrollKey,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
// backgroundColor: Colors.white,
expandedHeight: _expandedHeight,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(
left: 24,
right: 8,
),
height: 30,
decoration: BoxDecoration(
color: const Color.fromRGBO(220, 244, 243, .63),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Search Tasks',
textAlign: TextAlign.center,
style: Theme.of(context)
.primaryTextTheme
.bodyText1
.copyWith(color: Colors.white70, fontSize: 16),
),
),
flexibleSpace: FlexibleSpaceBar(
background: Stack(
children: [
Align(
child: Container(
height: 56 + 0.4 * MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF6EE2F5), Color(0xFF6E96F5)],
),
),
),
),
Align(
alignment: const Alignment(0, 0.5),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFEEF4), Color(0xFFE3FCFF)],
),
borderRadius: BorderRadius.circular(100),
boxShadow: const [
BoxShadow(color: Color.fromRGBO(0, 0, 0, 0.16))
],
),
child: Container(
alignment: Alignment.center,
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: '${(1 * 100).round()}%\n',
style: TextStyle(
color: const Color(0xFF5C74BC),
fontSize:
(1 * 100).round() == 100 ? 28 : 32,
fontWeight: FontWeight.bold,
),
),
const TextSpan(
text: 'Complete',
style: TextStyle(
color: Color(0xFF5C74BC),
fontSize: 13,
),
),
],
),
),
),
),
),
],
),
),
),
// ),
];
},
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
// backgroundColor: Colors.white,
toolbarHeight: _isPinnedTitleShown ? kToolbarHeight : 0,
pinned: true,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(
left: 24,
right: 8,
),
height: 30,
decoration: BoxDecoration(
color: const Color.fromRGBO(220, 244, 243, .63),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Search Tasks',
textAlign: TextAlign.center,
style: Theme.of(context)
.primaryTextTheme
.bodyText1
.copyWith(color: Colors.white70, fontSize: 16),
),
),
),
SliverExpandableList(
builder: SliverExpandableChildDelegate<String, ExampleSection>(
sectionList: sectionList,
headerBuilder: _buildHeader,
itemBuilder: (context, sectionIndex, itemIndex, index) {
final item = sectionList[sectionIndex].items[itemIndex];
return ListTile(
leading: CircleAvatar(
child: Text("$index"),
),
title: Text(item),
);
},
),
),
],
),
),
);
}
Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
final ExampleSection section = sectionList[sectionIndex];
return InkWell(
onTap: () {
//toggle section expand state
setState(() {
section.setSectionExpanded(!section.isSectionExpanded());
});
},
child: Container(
color: Colors.lightBlue,
height: 48,
padding: const EdgeInsets.only(left: 20),
alignment: Alignment.centerLeft,
child: Text(
"Header #$sectionIndex",
style: const TextStyle(color: Colors.white),
),
),
);
}
}
current solution must remain second SliverAppBar size, for smooth:
import 'package:flutter/material.dart';
import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
import 'mock_data.dart';
class ExampleSliver extends StatefulWidget {
const ExampleSliver({Key key}) : super(key: key);
@override
_ExampleNestedScrollViewState createState() =>
_ExampleNestedScrollViewState();
}
class _ExampleNestedScrollViewState extends State<ExampleSliver>
with TickerProviderStateMixin {
List<ExampleSection> sectionList = MockData.getExampleSections();
final GlobalKey<NestedScrollViewState> nestedScrollKey = GlobalKey();
final double _expandedHeight = 200;
final double _toolbarHeight = 30;
bool _isPinnedTitleShown = false;
@override
void initState() {
super.initState();
var headerContentHeight = _expandedHeight - _toolbarHeight;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
outerController.addListener(() {
var pinned = outerController.offset >= headerContentHeight;
if (_isPinnedTitleShown != pinned) {
setState(() {
_isPinnedTitleShown = pinned;
});
}
print("outerController position: $outerController $kToolbarHeight $_isPinnedTitleShown");
});
});
}
ScrollController get outerController {
return nestedScrollKey.currentState.outerController;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
key: nestedScrollKey,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
// backgroundColor: Colors.white,
expandedHeight: _expandedHeight - kToolbarHeight,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(
left: 24,
right: 8,
),
height: 30,
decoration: BoxDecoration(
color: const Color.fromRGBO(220, 244, 243, .63),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Search Tasks',
textAlign: TextAlign.center,
style: Theme.of(context)
.primaryTextTheme
.bodyText1
.copyWith(color: Colors.white70, fontSize: 16),
),
),
flexibleSpace: FlexibleSpaceBar(
background: Stack(
children: [
Align(
child: Container(
height: 56 + 0.4 * MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF6EE2F5), Color(0xFF6E96F5)],
),
),
),
),
Align(
alignment: const Alignment(0, 0.5),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFEEF4), Color(0xFFE3FCFF)],
),
borderRadius: BorderRadius.circular(100),
boxShadow: const [
BoxShadow(color: Color.fromRGBO(0, 0, 0, 0.16))
],
),
child: Container(
alignment: Alignment.center,
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: '${(1 * 100).round()}%\n',
style: TextStyle(
color: const Color(0xFF5C74BC),
fontSize:
(1 * 100).round() == 100 ? 28 : 32,
fontWeight: FontWeight.bold,
),
),
const TextSpan(
text: 'Complete',
style: TextStyle(
color: Color(0xFF5C74BC),
fontSize: 13,
),
),
],
),
),
),
),
),
],
),
),
),
// ),
];
},
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
elevation: 0,
leading: Visibility(
visible: _isPinnedTitleShown,
child: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
),
title: Visibility(
visible: _isPinnedTitleShown,
child: Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(
left: 24,
right: 8,
),
height: _toolbarHeight,
decoration: BoxDecoration(
color: const Color.fromRGBO(220, 244, 243, .63),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Search Tasks',
textAlign: TextAlign.center,
style: Theme.of(context)
.primaryTextTheme
.bodyText1
.copyWith(color: Colors.white70, fontSize: 16),
),
),
),
),
SliverExpandableList(
builder: SliverExpandableChildDelegate<String, ExampleSection>(
sectionList: sectionList,
headerBuilder: _buildHeader,
itemBuilder: (context, sectionIndex, itemIndex, index) {
final item = sectionList[sectionIndex].items[itemIndex];
return ListTile(
leading: CircleAvatar(
child: Text("$index"),
),
title: Text(item),
);
},
),
),
],
),
),
);
}
Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
final ExampleSection section = sectionList[sectionIndex];
return InkWell(
onTap: () {
//toggle section expand state
setState(() {
section.setSectionExpanded(!section.isSectionExpanded());
});
},
child: Container(
color: Colors.lightBlue,
height: 48,
padding: const EdgeInsets.only(left: 20),
alignment: Alignment.centerLeft,
child: Text(
"Header #$sectionIndex",
style: const TextStyle(color: Colors.white),
),
),
);
}
}```
This package has been really great for creating complex applications but I've had trouble now implementing the SliverExpandableList with a NestedScrollView. Specifically, the sticky header overlaps with the SliverAppBar I have in the headerSliver section of the NestedScrollView when the pin option is set to true. If I set pin to false there will no longer be any overlap but for the purposes of my application I need the appbar to be pinned.
I saw in another issue someone mentioned something similar and they used a SafeArea widget around the CustomScrollView to circumvent this, however this only fixes this issue when pin is set to false. Similarly, using a regular appbar within the scaffold with the toolbarheight set to 0 has the same effect as using the SafeArea widget. I tested with the example application listed to find a solution to no avail. This is what it looks with a SafeArea widget around the CustomScrollView vs without:
As you can see while the SafeArea widget does fix the header from being trapped in the statusbar, it is now stuck within the sliverappbar. After reading the NestedScrollView documentation here are the series of steps I've tried:
1) According to the NestedScrollView documentation, SliverOverlapAbsorber and SliverOverlapInjector should stop the body from scrolling underneath the sliverappbar, however this does not seem to be the case. It still refuses to respect the sliverappbar.
2) Setting floating to true instead of pinned does not work as intended either. The sliverappbar never appears when scrolling upwards even if I set "floatHeaderSlivers" of NestedScrollView to true.
3) Setting floating and snapped to true also does not resolve this issue. While it does show the sliverappbar when I scroll up now, it completely overlaps the body. Additionally, setting "floatHeaderSlivers" of NestedScrollView to true with these options does not change this behavior.
Any ideas on how to solve this issue? It would be greatly appreciated. The code below is based off the code from the example in this package.