I am developing a flutter app, in home screen i have tabs( each tab is showing a category) and by click one of them the category products will appear.
Just like that
The problem is when i click home first i must have a general products from all categories and no category should be selected first.
After the user see's the general products he can then choose category he wants to see.
So i must customize the tabs and i don't know how.
Any idea?
Here is a demo widget using DefaultTabController, you can archive it easily.
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
snap: true,
floating: true,
toolbarHeight: 80,
backgroundColor: Colors.white,
title: Container(
width: double.infinity,
color: Colors.redAccent,
child: Text("search here"),
),
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size(0.0, 48.0),
child: Column(
children: [
TabBar(
isScrollable: true,
indicatorWeight: 1,
labelColor: Colors.green,
unselectedLabelColor: Colors.grey,
// indicatorSize: TabBarIndicatorSize.label,
labelStyle: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
tabs: [
Tab(text: "screen1"),
Tab(text: "screen2"),
Tab(text: "screen3"),
Tab(text: "screen4"),
],
),
],
),
),
)
],
body: TabBarView(
children: [
Container(
child: Text("screen1"),
),
Container(
child: Text("screen2"),
),
Container(
child: Text("screen3"),
),
Container(
child: Text("screen4"),
),
],
)),
),
);
}
}
Related
I want to implement a float AppBar with a pinned TabBar at the bottom. The following is the test code (dartPad):
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: Colors.yellow,
title: Text(
"WhatsApp type sliver appbar",
),
centerTitle: true,
pinned: true,
floating: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Container(
color: Colors.orange,
alignment: Alignment.topLeft,
child: TabBar(
isScrollable: true,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
controller: _tabController,
labelPadding: EdgeInsets.only(
top: 13, bottom: 13, left: 16, right: 16),
tabs: [
Text(
"TAB A",
),
Text(
"TAB B",
),
]),
),
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
TabA(),
const Center(
child: Text('Display Tab 2',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
],
),
),
);
}
I find that it will have a top-padding on the TabBar when scrolling down. Is there any way to remove that space? I have set the SliverAppBar's toolbarheight, but that space will keep there even I lower the height.
scroll up (show appbar):
scroll down (hide appbar, the top yellow is not hidden):
Just set property pinned: false
See documentation
pinned → bool
Whether the app bar should remain visible at the start of the scroll view. [...]
final
Also remove tabBar from bottom: and add it above tabbarview inside a column
Thank you for the help.
Finally, I have another solution that may also take consider. I post here for others to ref.
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: Colors.yellow,
title: Text(
"WhatsApp type sliver appbar",
),
elevation: 0.0,
forceElevated: false,
pinned: false,
floating: true,
primary: false,
centerTitle: false,
titleSpacing: NavigationToolbar.kMiddleSpacing,
automaticallyImplyLeading: false,
),
SliverAppBar(
backgroundColor: Colors.orange,
pinned: true,
primary: false,
centerTitle: false,
titleSpacing: 0,
toolbarHeight: 48,
automaticallyImplyLeading: false,
forceElevated: true,
title: Align(
alignment: Alignment.topLeft,
child: TabBar(
isScrollable: true,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
controller: _tabController,
labelPadding: EdgeInsets.only(
top: 13, bottom: 13, left: 16, right: 16),
tabs: [
Text(
"TAB A",
),
Text(
"TAB B",
),
]),
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
TabA(),
const Center(
child: Text('Display Tab 2',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
],
),
),
);
}
Basically, what i do is separate 2 SliverAppBar, one is not pinned and floating; another is pinned. This makes the upper appbar disappear when scroll down and display when scroll up while the tabbar will keep pinning there.
İf you use listview add this line
ListView.builder(
padding: EdgeInsets.zero,
Like the title, i have created a button with Navigator.push() into a new page, the page auto generates a back button on the appBar, i appreciated that autogenerated back button but i want the back button to not displace my title's positioning, is that possible?
Below is my code for the button to enter this page:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return FeedTest();
},
),
);
This is my full code for the new page:
Widget build(BuildContext context) {
return ScrollConfiguration(
behavior: MyBehavior(),
child: SafeArea(
child: DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.grey[850],
title: Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/logo2.png',
isAntiAlias: true,
scale: 1.8,
fit: BoxFit.fitHeight,
alignment: Alignment.center,
)
]),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: [
Tab(
text: 'Submit Feedback',
),
Tab(
text: 'Submit Testimony',
)
],
),
),
body: TabBarView(
children: [SubmitFeedback(), SubmitTestimony()],
),
),
),
),
);
}
P.S. Title image has been blurred for privacy purposes
centerTitle: true is used to make the title center in AppBar
AppBar(
centerTitle: true,
...
)
Output:
Use centerTitle: true like below.
AppBar(
centerTitle: true, // this is all you need
//Rest Code here
)
I upload the full code like below.
Widget build(BuildContext context) {
return ScrollConfiguration(
behavior: MyBehavior(),
child: SafeArea(
child: DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
centerTitle: true,
elevation: 0,
backgroundColor: Colors.grey[850],
title: Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/logo2.png',
isAntiAlias: true,
scale: 1.8,
fit: BoxFit.fitHeight,
alignment: Alignment.center,
)
]),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: [
Tab(
text: 'Submit Feedback',
),
Tab(
text: 'Submit Testimony',
)
],
),
),
body: TabBarView(
children: [SubmitFeedback(), SubmitTestimony()],
),
),
),
),
);
}
I'm trying to implement a custom tab bar widget inside a SliverAppBar. So far I've tried wrapping my CustomTabBar within a PreferredSize widget.
Here's my code:
Widget _buildBody(){
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
return <Widget>[
SliverAppBar(
leading: Container(),
backgroundColor: Colors.transparent,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
"Item 1"
),
Text(
"Item 2"
),
Text(
"Item 3"
)
],
)
]),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: CustomTabWidget(
items: ['Challenge', 'My friends'],
activeColor: secondaryColor,
currentIndex: currentIndex,
backgroundColor: tabColor,
activeTextColor: Colors.white,
backgroundTextColor: Colors.white,
onTabSelect: (int index) {
onLeaderboardTabSelect(index);
},
),
),];
},
body: ListView.separated(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('row $index'),
);
},
separatorBuilder: (context, index) {
return Divider();
},
) // should return listview depending on the tab
);
}
CustomTabWidget
Widget build(BuildContext context) {
return Container(
height: height,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(
30.0,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildItems(context),
),
);
}
The code successfully shows the my custom tab bar widget but whenever I scroll down or tap another tab, it disappears.
I might have overlooked something within the code.
Can anyone help me?
this work on my project
class TabBarInSliverAppbar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text("Tabbar in SliverAppbar Example"),
pinned: true,
floating: true,
bottom: TabBar(
tabs: <Widget>[
Tab(
text: "First Tab",
),
Tab(
text: "Second Tab",
),
],
),
),
SliverToBoxAdapter(
child: TabBarView(
children: <Widget>[
Center(
child: Text("First Tab"),
),
Center(
child: Text("Second Tab"),
),
],
),
),
],
),
),
);
}
}
you can also checki this link :Flutter TabBar and SliverAppBar that hides when you scroll down
I'm want to display a tabbar as it is used with the appbar, but in the middle of the scaffold body. Is it possible?
I have this code, but the TabBarView breaks the ui. If I comment the TabBarView, the TabBars are displayed correctly. What is wrong with the code? In case it's possible to use it this way..
#override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Text('Some other random content'),
TabBar(
controller: _tabController,
isScrollable: true,
tabs: <Widget>[
Tab(
child: Text(
'Posts',
style: TextStyle(
fontSize: 18.0,
color: Colors.black87,
),
),
),
Tab(
child: Text(
'Fotos',
style: TextStyle(
fontSize: 18.0,
color: Colors.black87,
),
),
),
],
),
TabBarView(
controller: _tabController,
children: <Widget>[
Center(
child: Text("User"),
),
Center(
child: Text("Email"),
),
],
),
],
),
);
}
Just Change ListView to Column and add SingleChildScrollView
SingleChildScrollView(
child: Column(
children: <Widget>[]
),
)
Hey i guess it's already too late but maybe it helps others.
If you just want to have your tabbar e.g. in the middle of the screen to divide your screen in 2 sections (top: content, bottom: tabbar) you can place your scaffold inside a column, e.g. following:
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
flex: 1,
child: Center(
child: Text("Header"),
),
),
Expanded(
flex: 1,
child: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
flexibleSpace: TabBar(
tabs: [
Tab(text: "First"),
Tab(text: "Second"),
Tab(text: "Third"),
],
),
),
body: TabBarView(
children: [
Center(child: Text("First screen")),
Center(child: Text("Second screen")),
Center(child: Text("Third screen")),
],
),
),
),
),
],
);
}
Let me know if this fits your needs / if you need further help :-)
I want my appbar to act as a fixed appbar when it's scrolled down, or user's searching something.
SliverAppBar(
title: new TextField(
style: Theme.of(context).primaryTextTheme.title,
decoration: InputDecoration(
hintText: '검색',
),
),
),
But I want to draw it as a flexible appbar when it's scrolled up and user's not searching.
flexibleSpace: new FlexibleSpaceBar(
centerTitle: false,
title: new TextField(
style: Theme.of(context).primaryTextTheme.title,
decoration: InputDecoration(
hintText: '검색',
),
),
background: Stack(
fit: StackFit.expand,
children: <Widget>[
SizedBox(
height: 256.0,
child: Container(
child: Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Column(
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlutterLogo(
size: 64.0,
),
),
Padding(padding: const EdgeInsets.only(bottom: 24.0)),
ListTile(
title: Text('Some Text'),
),
],
),
),
),
),
// This gradient ensures that the toolbar icons are distinct
// against the background image.
],
),
),
Search field is transformed to top-right little bit when scrolled up with second approach.
The effect can be achieved by moving title content to another SliverList.
Remove flexibleSpace from SliverAppBar, and move contents of flexibleSpace.background to SliverList before the SliverAppBar.
Example:
#override
Widget build(BuildContext context) {
return new CustomScrollView(
slivers: <Widget>[
new SliverList(
delegate: new SliverChildListDelegate(<Widget>[
Align(
alignment: Alignment.topLeft,
child: FlutterLogo(size: 64.0),
),
ListTile(
title: Text('Some Text'),
),
ListTile(),
])),
new SliverAppBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
elevation: 0.0,
automaticallyImplyLeading: false,
pinned: true,
floating: false,
title: new TextField(
focusNode: _searchFocusNode,
style: Theme.of(context).primaryTextTheme.title,
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
suffixIcon: Icon(Icons.search),
hintText: '검색',
),
),
),
new SliverList(
delegate: new SliverChildListDelegate(List.generate(
100,
(i) => ListTile(
title: Text('Scroll'),
)).toList()),
),
],
);
}
I've written a getMaterialSerach(); method in this gist which has the exact material search view you need. just add getMaterialSearch() from this in your appBar: widget like below.
here is the gist for getMaterialSearch();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: getMaterialSearchBar(),
body: Center(
child: Container(),
),
);
}
Here's my code with a TextField and Tabs in a SliverAppBar:
NestedScrollView(
controller: model.mainScrollController,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
/// https://github.com/flutter/flutter/issues/54059
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
automaticallyImplyLeading: false,
pinned: true,
floating: true,
snap: true,
expandedHeight: 100,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
children: [
BackButton(
color: Colors.white,
),
Flexible(
child: TextField(
controller: model.searchController,
decoration: InputDecoration(
focusColor: Colors.blueAccent,
hoverColor: Colors.blueAccent,
fillColor: Colors.white,
filled: true,
isDense: true,
prefixIconConstraints: BoxConstraints(maxHeight: 24, maxWidth: 48),
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Icon(Icons.search_outlined),
),
hintText: 'Search...'),
),
),
IconButton(
icon: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
ExtendedNavigator.named('topNav').push(Routes.newExerciseView);
},
tooltip: 'Create Exercise',
),
],
),
),
),
bottom: TabBar(
labelPadding: EdgeInsets.only(bottom: 8),
indicatorWeight: 3,
indicatorSize: TabBarIndicatorSize.label,
tabs: [
Text('All'),
Text('Custom'),
Text('Favorites'),
],
),
),
),
];
},
body: TabBarView(
children: [
AllExercises(),
CustomExercises(),
FavoriteExercises(),
]),
),
There's a few key pieces that make this work right, the first being that you need to use the SliverAppBar's flexibleSpace property to add your search widget. If you try adding it in the title property, the TextField just get's squished but never goes off screen.
Second, make sure that the collapseMode of the FlexibleSpaceBar is set to CollapseMode.pin. This makes it so that the contents of the flexibleSpace scroll off screen and don't change size.
And finally, set pinned on the sliverAppBar to true so the TabBar sticks.