Hi I was wondering it is possible to have a grid view within a tabbarview which has properties of a sliver so it moves the app bar.
I have a grid view within a tabbarview however it does not cause the tab bar to collapse
This is my code so far
NestedScrollView(
headerSliverBuilder: (context, value) {
return SliverAppBar(
backgroundColor: Colors.white,
floating: true,
pinned: false,
stretch: false,
snap: false,
(...)///Collapsible space etc
);
},
body: TabBarView(
children: [
///Other widgets
(...)
///My gridview
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2),
children: List.generate(10, (index) {
return Container(
height: 100.0,
width: 100.0,
color: Colors.pink,
child: Text(index.toString()),
);
}),
)
]
)
)
Edit Below
[EDIT]
Set shrinkWrap to true
Sets physics to NeverScrollableScrollPhysics()
set shrinkWrap to true and physics to NeverScrollableScrollPhysics() to your GridView.
This is useful when you add a scrollable widget inside another scrollable widget.
Related
I'm having a bit of annoyance. I want a scroll bar present in my flutter web 'page' at all times, to indicate to the user that there's still some elements to view them. And I achieved this using scrollbar isAlwaysShown to true in my Theme class.
Now, when I use GridView.builder to generate elements on the screen, I must provide height constraints beforehand, or else I'll get an error that says 'height is infinite'.
The problem with this is, there are 2 scroll bars visible at all times. One from the SingleChildScrollView, and one from the GridView.builder. I need the SingleChildScrollView with the column so I can have a footer at the bottom of the page.
My question is, how can I get rid of the GridView.builder scroll bar?
Thanks in advance...
My code:
Scaffold(
key: _scaffoldKey,
appBar: AppBar(),
drawer: const DrawerWidget(),
body: SingleChildScrollView(
child: Column(
children: [
Container(
constraints: BoxConstraints(maxHeight: _size.height),
margin: Responsive.isDesktop(context)
? const EdgeInsets.symmetric(vertical: 10)
: null,
padding: Responsive.isDesktop(context)
? desktopPadding
: smallAndMediumPadding,
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemCount: 12,
itemBuilder: (BuildContext context, int index) {
return GridCard(name: '', image: '');
}
)
),
/////////////// Footer ///////////////////
Footer()
]
)
)
)
Actually you can remove the BoxConstraints from your Container and to solve the height issue you have a property in GridView.builder named shrinkWrap. You just need to set it true and it will solve your height issue.
shrinkWrap: true,
and after that you may have issue with Scrolling the GridView.builder so to solve that use another property primary and set it to false
primary : false,
You shouldn't use multiple Scrollables inside each other to get footer functionality. By using shrinkWrap on the inner scrollable (GridView) you'd effectively turn it into a regular unscrollable widget and use scrolling of the outer view (SingleChildScrollView). It is terrible for performance because Flutter would have to build, layout and (probably) render entire GridView including all the items every time.
You should instead use CustomScrollView to display your grid and footer like this:
Scaffold(
body: CustomScrollView(
slivers: [
SliverPadding(
// not sure what you were trying to do with your padding
padding: (Responsive.isDesktop(context)
? const EdgeInsets.symmetric(vertical: 10)
: EdgeInsets.zero) +
(Responsive.isDesktop(context)
? desktopPadding
: smallAndMediumPadding),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
delegate: SliverChildBuilderDelegate(
(context, index) => GridCard(name: '', image: ''),
childCount: 12,
),
),
),
SliverToBoxAdapter(
child: Footer(),
),
],
),
)
I am building a chat app and I want new chat messages arrive from the bottom of the SliverList. As well I want the SliverAppBar to scroll out of view (floating) when the scroll view starts scrolling when enough messages have arrived.
I am using a reversed CustomScrollView with an SliverAppBar and a SliverList:
CustomScrollView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
reverse: true,
controller: _scrollController,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return _messages[index];
},
childCount: _messages.length,
),
),
SliverAppBar(
title: Text('Chat App'),
centerTitle: true,
floating: true,
),
],
),
This way the SliverAppBar is on top of the SliverList, but not at the top of the screen. Is it possible to set the SliverAppBar to the top of the screen as if it was not reversed?
A Simple example would be:
Scaffold(
floatingActionButton: _fab(),
floatingActionButtonLocation: _fabLocation(),
body: Scrollbar(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
automaticallyImplyLeading: false,
leading: null,
backgroundColor: Theme.of(context).colorScheme.secondary,
title: AutoSizeText(
_catalogSearchRatio,
style: Theme.of(context).textTheme.title.copyWith(
color: Theme.of(context).colorScheme.onSecondary),
),
floating: true,
pinned: false,
),
SliverFixedExtentList(
itemExtent: 90.0,
delegate: SliverChildBuilderDelegate((context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 5.0, vertical: 5.0),
child: CatalogItems.medidaGroups
.contains(_itemsCatalogDisplay[index].group)
? _medidasCard(
index, _itemsCatalogDisplay, _catalogSearchQuery)
: _catalogCard(index, _itemsCatalogDisplay,
_catalogSearchQuery, _cartItems, _filteredGroups),
);
}, childCount: itemsCatalogDisplayLength),
),
],
controller: _catalogScrollController,
),
),
);
In the above example, a Scrollbar can be used and that's fine. BUT you can't drag the scrollbar. So I am using a package called draggable_scrollbar which lets me drag the scrollbar. However, I can't seem to replace the scrollbar I have in the example because it gives me the can't be assigned to BoxScrollView error.
It works if it's just a Listview or a Listview.builder or even a GridView
Start by importing the Cupertino package like below..
Import 'package: flutter/cupertino.dart';
Now, inside your class which contains your ScrollViews add the ScrollController like below...
final ScrollController myScrollWorks = ScrollController();
Now wrap any of your ScrollViews... which in your case is a CustomScrollView with the code below
body: PrimaryScrollController(
controller: myScrollWorks,
child: CupertinoScrollbar(
child: CustomScrollView(
slivers:[
your slivers code here
];
I'm trying to display a flexible SliverAppBar below a custom app bar (let's say it's a container with a height of 80.0).
When making the SliverAppBar the top element, it works fine, but when it's the second one, there is a top-padding as big as the Android UI interface.
Scaffold(
body: Column(children: <Widget>[
Container(height: 80.0),
Expanded(child: _content())
]),
);
_content()
return CustomScrollView(slivers: <Widget>[
SliverAppBar(
backgroundColor: Colors.red,
leading: PopContentButton(),
title: Text('Test'),
snap: true,
pinned: true,
floating: true,
bottom: TabBar(
tabs: _tabs(),
controller: TabControllerExtended(length: 4, vsync: this),
),
),
SliverList(delegate: new SliverChildListDelegate(buildTextViews(50)))
]);
This is not how it should look:
(source: bilder-upload.eu)
It should look like:
(source: bilder-upload.eu)
Wrap you - SliverAppBar with MediaQuery.removePadding.
Updated Code :
....
MediaQuery.removePadding(
context: context,
removeTop: true,
child: SliverAppBar(
...
I have a NestedScrollView which works well to AutoHide the AppBar (one feature I want) when I use SliverAppBar. Where I am running into problems, is I use ListView.Builder as one of the body components downstream that I need apply its own ScrollController to (or seems I need to apply it here). This conflicts with the NestedScrollView and I lose the autohide of the appbar that is conveniently handled by the NestedScrollView and SliverAppBar.
If I attach the ScrollController on the NestedScrollView Then it only tracks scroll position up to an offset of 80.0 and after that, with a longer ListView I am unable to properly animateTo as I can with the ScrollController attached directly to the ListView.Builder.
Here is a snippet/sudo code of my implementation:
new Scaffold(
drawer: ...,
body: new NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
new SliverAppBar(
title: new Text('Title'),
floating: true,
snap: true
)
]
}
body: new Stack(
children: <Widget>[
new PageView(
children: <Widget>[
new PageView1(implements ListViewBuilder),
new PageView2(implements ListView),
new PageView3(implements ListView),
]
controller: _pageController,
),
new FloatingActionButton
]
)
)
)
class PageView1 extends StateFulWidget {
...//Builder return scrollable with max offset of 2000.0
return new ListView.builder(
itemBuilder: itemBuilder,
itemCount: objects.length,
controller: _scrollController,
);
...
#override
void initState{
scrollController = new scrollController();
scrollController.animateTo(800.0, ....);
}
}
The nice part about this is the PageView2 and 3 behave nicely, with the autohide of the app bar on scroll behavior as I am not creating ScrollControllers there. But, PageView1 behaves incorrectly and autoHide of the appbar breaks. But, I really want to be able to animateTo correctly and am unable to do so without placing the controller directly on the ListViewBuilder.
Any thoughts on a better implementation that would help me achieve this?
UPDATE: I have updated my implementation to more closely follow the NestedScrollView documentation. But, with no luck. It seems the addition of the ScrollController on the NestedScrollView only tracks the position of the SliverAppBar, and ScrollController.jumpTo or animateTo only jump to a maximum of the AppBar (offset 80)
I worked it out.. This is not how I expected it to work at all. I moved my SliverList into the headerSliverBuilder and it works the way I want it to. I took the cue to do so from this NestedScrollView example gist: https://gist.github.com/collinjackson/2bc6697d31e6b94ada330ef5e818a36f
Follow the NestedScrollViewExample:
Change your list view to SliverList or SliverFixedExtentList and Wrap it inside a safe area and a CustomScrollView:
return SafeArea(
top: false,
bottom: false,
child: Builder(builder: (BuildContext context) => CustomScrollView(
slivers: <Widget>[
return SliverFixedExtentList(
itemExtent: 100.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) => ChildWidget(items[i]),
childCount: items.length,
),
),
],
)),
);
I met with the same problem and here is my solution. You can add a key to NestedScrollView and use it to access the inner CustomScrollView/ListViewcontroller of the tab, you only need to add the ScrollController to NestedScrollView
final GlobalKey<NestedScrollViewState> documentsNestedKey = GlobalKey();
void initState() {
_scrollController = ScrollController();
_tabController = TabController(length: 2, vsync: this);
_tabController.addListener(() {
setState(() {});
});
// Tabs Pagination
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
documentsNestedKey.currentState!.innerController.addListener(() {
if (documentsNestedKey.currentState!.innerController.positions.last.atEdge) {
if (documentsNestedKey.currentState!.innerController.positions.last.pixels != 0) {
if (_tabController.index == 0) {
context.read<DeclarationsBloc>().add(const FetchDeclarationsEvent());
} else {
context.read<TasksBloc>().add(const FetchTasksEvent());
}
}
}
});
});
super.initState();
}
return SafeArea(
child: NestedScrollView(
key: documentsNestedKey,
controller: _scrollController,
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
backgroundColor: theme.background,
floating: true,
pinned: true,
forceElevated: innerBoxIsScrolled,
flexibleSpace: FlexibleSpaceBar(
background: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 11),
Padding(
padding: responsiveUtil.getHorizontalPadding(),
child: SearchWithFilter(
searchController: widget.searchController,
currentMenuIndex: 3,
onSearch: (text) {},
onSearchClear: () {},
onFilterTap: widget.onFilterTap,
),
),
],
),
),
bottom: DeclarationsTabBar(
tabController: _tabController,
),
),
),
],
body: TabBarView(
controller: _tabController,
children: [
MyDeclarationsTab(searchController: widget.searchController),
TasksTab(searchController: widget.searchController),
],
),
),
);