CustomScrollView(
SliverToBoxAdapter(), // firstBox
SliverPersistentHeader(pinned:true),
SliverToBoxAdapter(), // secondBox
)
I have a button on this page, when I click this button, how let SliverPersistentHeader scroll to top programmatically?
Addition: firstBox's height is uncertain.
Initalize a GlobalKey in that widget.
final GlobalKey globalKey = GlobalKey();
Then pass it to the widget you want to scroll to:
CustomScrollView(
slivers: [
SliverToBoxAdapter(), // firstBox
SliverPersistentHeader(pinned: true, delegate: null,),
SliverToBoxAdapter(
key: globalKey,
),
],
)
Then the button onPressed should be like this:
onPressed: () {
Scrollable.ensureVisible(
globalKey.currentContext!,
duration: const Duration(seconds: 1),
);
},
Related
So basically I have this widget:
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// ...
SliverToBoxAdapter(
child: TabBar(
controller: this._controller,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 1.0, color: Colors.red),
),
tabs: [
Tab(icon: Icon(CupertinoIcons.camera)),
Tab(icon: Icon(CupertinoIcons.photo)),
Tab(icon: Icon(CupertinoIcons.video_camera)),
],
),
),
SliverFillRemaining(
child: TabBarView(
controller: this._controller,
children: [
// Want Scrollable Grid here
// Want Scrollable Grid here
Center(
child: Text("Hello Reader🙂"),
),
],
),
),
// ...
],
),
);
}
}
I want to add a 2 scrollable grids as children in the TabBarView however when I use GridView.builder(...), there is an annoying gap at the top of the grid and scrolling isn't all too great neither even with shrinkWrap: true and physics: NeverScrollableScrollPhysics().
However when I use a SliverGrid(...), there is this error
RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
This obviously makes sense because TabBarView isn't a sliver widget. I have already taken a look at this post but it wasn't really of any help.
How could I implement this? Is there perhaps a way I could create my own widget builder that builds a custom layout?
Thank You!
You need to use SliverOverlapAbsorber/SliverOverlapInjector, the following code works for me (working full code on dart pad):
Here i used SliverFixedExtentList but you can it replace with SliverGrid.
#override
State<StatefulWidget> createState() => _NewsScreenState();
}
class _NewsScreenState extends State<NewsScreen> {
final List<String> listItems = [];
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
primary: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs
.map((String name) => Tab(text: name))
.toList(),
),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is "inside"
// the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
// find the NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 60.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return Container(
color: Color((math.Random().nextDouble() *
0xFFFFFF)
.toInt() <<
0)
.withOpacity(1.0));
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
),
);
}
}
I am very new to flutter and was just curious to know how can we create a new card widget everytime a button (lets say FAB) is clicked.
Suppose this is the card widget :
return Card(
child: Column(
children: [
Text('name'),
Text('standard'),
Text('Roll No'),
],
),
);
I want the cards to build with the same content everytime the FAB is clicked. Can someone help me with this ?
First declare a List of widget type
List<Widget> _cardList = [];
Then create your widget which you want to add on button click, e.g.
Widget _card() {
return Card(
child: Column(
children: [
Text('name'),
Text('standard'),
Text('Roll No'),
],
),
);
}
add your widget in List on button click
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
_cardList.add(_card());
});
},
),
Now use a ListView.builder for create a list of widget
ListView.builder(
shrinkWrap: true,
itemCount: _cardList.length,
itemBuilder: (context, index) {
return _cardList[index];
},
)
I am building a WebApp in flutter and I have a SingleChildScrollView with some widgets inside. I want the buttons on the appbar to take me to the correspondent widget when I press on the buttons.
Is that possible? Here I attach the part of the code.
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: CustomAppBar(),
backgroundColor: Colors.white,
body: SingleChildScrollView(
controller: widget.homeController,
child: Column(
children: [
Inicio(),
Services(),
QuienesSomos(),
ContactForm(),
BottomInfo(),
],
),
),
);
}
So I have one button on the appbar per each children in the SingleChildScrollView and I would like that when I press the correspondent button, it scrolls down to the correspondent section on the widget. I tried with Navigator.of().PushNamed but it opens a new screen instead of scrolling down. Any ideas? Thanks in advance!
To control the position, you have to manage the controller of the SingleChildScrollView .
If you want to smoothly go a section, you can attach functionality to control the SingleChildScrollView controller to the button:
widget.homeController.animateTo(
0.0, // change 0.0 {double offset} to corresponding widget position
duration: Duration(seconds: 1),
curve: Curves.easeOut,
);
If you just want to instantly jump to the position:
widget.homeController.jumpTo(0.0); // change 0.0 {double value} to corresponding widget position
Make a scroll controller:
ScrollController myController = ScrollController();
and attach it to your SingleChildScrollView widget:
SingleChildScrollView(
controller: myController,
child: ...
Now create a GlobalKey:
final anchor = GlobalKey();
Attach it to any of your widget:
Container(
key: anchor,
child: ...
),
That's it, now you can programmatically scroll to this widget using scroll controller:
myController.position.ensureVisible(
anchor.currentContext.findRenderObject(),
alignment: 0.5,
duration: const Duration(seconds: 1),
);
I could achieve my goal by using,
onPressed: () {
Scrollable.ensureVisible(servicesKey.currentContext,
duration: Duration(seconds: 1),
curve: Curves.easeOut);
},
and by asigning the corresponding key in each widget.
#override
Widget build(BuildContext context) {
super.build(context);
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
child: Scaffold(
key: _scaffoldKeyProfilePage,
body: DefaultTabController(
length: 2,
child:RefreshIndicator(
onRefresh: _onRefresh,
child: NestedScrollView(
headerSliverBuilder: (context, _) {
return [
SliverList(
delegate: SliverChildListDelegate(
[ BuildMainProfile(
....//
),
Padding(
...//another design
),
];
},
// You tab view goes here
body: Column(
children: <Widget>[
TabBar(
tabs: [
Tab(text: 'A'),
Tab(text: 'B'),
],
),
Expanded(
child: TabBarView(
children: [
BuildPost(,
),
BuildWings()
],
),
),
],
),
),),
),
}
Refresh Indicator not working with NestedScrollView, is there any way to implement RefreshIndiactor?
I tried adding an empty ListView in Stack, Refresh indicator start showing but because of that my NestedScrollView doesnt scroll.
Did you try setting NestedScrollView widgets physics to AlwaysScrollableScrollPhysics()?
Please see the documentation for RefreshIndicator.
Refresh indicator does not show up
The RefreshIndicator will appear if its scrollable descendant can be
overscrolled, i.e. if the scrollable's content is bigger than its
viewport. To ensure that the RefreshIndicator will always appear, even
if the scrollable's content fits within its viewport, set the
scrollable's Scrollable.physics property to
AlwaysScrollableScrollPhysics:
ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: ... )
A RefreshIndicator can only be used with a vertical scroll view.
Please try to wrap 'body' of the NestedScrollView with RefreshIndicator widget if the headers won't change when refreshed but only the content change happens for 'body'.
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),
],
),
),
);