Related
At my mobile apps homepage i have tabbar with two tabs act as category. both of those tabs display a listview with content depends on the category. (for example, a listview of book with fiction and non-fiction category.) and i want to add a single search function to search a book regardless their category.
The problem is:
One search bar can only search on one category. means the search bar can only search on fiction category. i try to add 2nd search bar in each tab but in result it act like the problem below
that whenever i click on the 2nd tab and then click on search bar, it will automatically go back to first tabs which is the default tab.
Solution that i think of:
Is to add setState so the tabs doesnt move back to default tab whenever i click on the search bar, but i dont know how.
Here is my code:
// Tab Bar MOF and CIDB
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16)
),
color: Theme.of(context).primaryColor,
),
width: double.infinity,
child: TabBar(
controller: _tabController,
indicatorSize: TabBarIndicatorSize.tab,
indicatorPadding: EdgeInsets.all(6),
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Colors.red, width: 100.0),
insets: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 42.0),
),
unselectedLabelColor: Colors.grey,
tabs: [
Tab(child: Text('MOF', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w300, ))),
Tab(child: Text('CIDB', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w300)))
]
),
),
SizedBox(
height: 30,
),
//Space for Tender Listview
Expanded(
child: Container(
width: double.infinity,
//Tender List tile
child: TabBarView(
controller: _tabController,
children: [
//MOF List
Column(
children: [
Expanded(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: MOFs.length,
itemBuilder: (context, index) {
final MOF = MOFs[index];
return Card(
margin: EdgeInsets.fromLTRB(15, 20, 15, 20),
child: InkWell(
onTap: (){},
child: Container(
margin: EdgeInsets.all(4),
height: 200,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12),
//color: Colors.blue
),
child: Center(child: Text(MOF.title)),
),
),
);
}),
),
Container(
//color: Colors.pink,
margin: EdgeInsets.fromLTRB(150, 10, 150, 10),
child: TextField(
controller: controller,
onChanged: searchMOF,
decoration: InputDecoration(
hintText: 'Search by Keyword',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(color: Colors.green))
),
),
),
],
),
//CIDB List
Column(
children: [
Expanded(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: CIDBs.length,
itemBuilder: (context, index) {
final CIDB = CIDBs[index];
return Container(
margin: EdgeInsets.all(10),
height: 200,
width: 50,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20),
//color: Colors.purple
),
child: InkWell(
onTap: (){},
child: Card
(child: Center(child: Text(CIDB.title))),
),
//List testing
);
}),
),
Container(
//color: Colors.pink,
margin: EdgeInsets.fromLTRB(150, 10, 150, 10),
child: TextField(
controller: controller,
onChanged: searchCIDB,
decoration: InputDecoration(
hintText: 'Search by Keyword',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(color: Colors.green))
),
),
),
],
),
],
),
),
),
],
),
);
}
You can make the search bar works globally by creating a parent stateful widget with search bar and child widget with tabs and tabs' body. In each tab body, you can check if search text field controller is not empty and if yes, you make only the books that contains search text appears and if no, you make the whole list appears
the whole step is too wide. i'll suggest some outline.
first of all, you have to know behaviour of ListView in flutter:
Creation and Destruction
Only visible children will be rendered in a listview. When a child is
scrolled out of view, the associated element subtree, states and
render objects are destroyed
second is the lifecycle. When you call setState it means flutter re-execute build method. the method below.
#override
Widget build(BuildContext context) {
return
thats why, everytime you call setState, the tabs is back to first tab. because it execute build method from the begining.
how to avoid rebuild entire widget is: Separate into new class.
eg:
class TabMOF extend StatelessWidget{
...
#override
Widget build(BuildContext context) {
return your MOF list
...
class TabCIBD extend StatelessWidget{
...
#override
Widget build(BuildContext context) {
return your CIBD list
then use is as a widget in your main Screen
...
body : Column(
children: [
// here will be your search bar
...
// next is your tabbar
Container(
child: TabBar(
tabs: [tab1, tab2 ]
)
Expanded(
child: TabBarView(
children:[
TabMOF(),
TabCIBD(),
]
]
with this way, your tab will not rebuild. but you have to update the List data for each tab.
if you need to rebuild your stateFullwidget tab, you can change the valuekey. article
actually its hard to explain very detail, hope you got what i mean. thank you
Am implementing this plugin here and in the body i create a screen that uses SingleChildScrollView to show some text fields,
Like the documentation says
Body: The Widget that lies underneath the sliding panel. This Widget
automatically sizes itself to fill the screen.
The problem is that when I give focus to one of the text fields the keyboard appears and hides what I am writing,
I use the plugin passing a the widget i want to show behind
SlidingUpPanel(
...
body: Padding(
padding: EdgeInsets.only(top: 160 + padding!.top),
child: behindWidget,
),
...
)
then this
I was trying to enclose my SingleChildScrollView inside a scaffold to use this: resizeToAvoidBottomInset: true, but that doesn't seem to make any difference, does anyone have any idea what I can do? and if there is a way to do it directly from the TextFormField would it be better?
I also have to say that I am using a DefaultTabController and in the first tab I enclose my SingleChildScrollView
like this:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Padding(
padding: EdgeInsets.only(top: 10),
child: DefaultTabController(
initialIndex: 0,
length: myTabs.length,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
bottom: TabBar(
labelColor: Colors.black,
labelStyle: smallTextBoldBlack,
unselectedLabelColor: unselectedColor,
unselectedLabelStyle: smallTextUnselectedSecondary,
indicatorColor: Colors.black,
controller: _tabController,
tabs: myTabs,
),
title: Text(
'Create a new Event',
style: subTitle,
),
),
body: TabBarView(
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
controller: _tabController,
children: [
_buildCreateEventView(),
Text("b"),
],
),
),
),
),
);
}
_buildCreateEventView()
Widget _buildCreateEventView() {
return ExpandableCalendar(
eventList: [],
padding: EdgeInsets.only(top: 20),
onDateSelected: (DateTime selectedDate) => {
if (this.mounted)
setState(() {
currentDate = selectedDate;
})
},
behindWidget: Scaffold(
resizeToAvoidBottomInset: true,
body: Container(
padding: EdgeInsets.only(left: 10, top: 20, right: 10, bottom: 10),
color: Colors.white,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width,
maxHeight: MediaQuery.of(context).size.height,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.only(top: 10, bottom: 10, left: 5),
child: TextFormField(
controller: eventNameController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter Event Summary',
),
),
),
Container(
padding: EdgeInsets.only(top: 10, bottom: 10, left: 5),
child: TextFormField(
controller: eventDescriptionController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter Event Description',
),
),
),
],
),
),
),
),
));
}
I'm not sure how your ExpandableCalendar class works but I suspect it's what is preventing Flutter from resizing the Scaffold body to move the text fields above the keyboard. If you try temporarily removing ExpandableCalendar and you see the widgets moving above your keyboard that should give you a clue to the underlying issue.
Add One more Container inside SingleChildScrollView and remove Scaffold
Container
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
SingleChildScrollView
Container
ConstrainedBox
I created a menu list with GridView. But when I scroll, it fail because scroll inside listview hold parent element to scroll like this image
My issue is gone when I try to wrap my GridView inside IgnorePointer. But if I use IgnorePointer it is not allow me to click / tap my menu item.
How the best way to fix my issue?
My code (without IgnorePointer)
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 10.0, bottom: 10.0),
width: MediaQuery.of(context).size.width,
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 4,
children: List.generate(
menuItems.length,
(index) {
return Column(
children: <Widget>[
InkWell(
onTap: () {
showShortToast("Menu clicked " + index.toString());
},
child: Container(
width: 70.0,
height: 70.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12.0)),
border: Border.all(color: Colors.grey[300]),
),
child: Center(
child: Image.asset(menuItems[index]["image"]),
),
),
),
Expanded(
child: Text(
menuItems[index]["text"],
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.0,
),
),
),
],
);
},
),
),
);
}
If you want to disable the scroll in the grid view you can do as follows:
child: GridView.count(
physics: NeverScrollableScrollPhysics(), //Add physics
shrinkWrap: true,
crossAxisCount: 4,
...
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.
I'm trying to make a profile page, where the users info is at the top. And then have a tab view below that for different views.
This is the code I'm using at the moment, when I take the TabBarView out it doesn't through an error, and if I wrap the TabBarView in an Expanded the error RenderFlex children have non-zero flex but incoming height constraints are unbounded. comes up.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(''),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
CircleAvatar(
minRadius: 45.0,
backgroundImage: NetworkImage(
'https://www.ienglishstatus.com/wp-content/uploads/2018/04/Anonymous-Whatsapp-profile-picture.jpg'),
),
Padding(
padding: EdgeInsets.only(left: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Testing Name',
style: TextStyle(
fontSize: 22.0,
color: Colors.grey.shade800,
),
),
Text(
'#testing_username',
style: TextStyle(
fontSize: 13.0,
color: Colors.grey.shade800,
),
),
],
),
),
],
),
),
DefaultTabController(
length: 3,
child: Column(
children: <Widget>[
TabBar(
tabs: <Widget>[
Tab(
icon: Padding(
padding: EdgeInsets.all(6.0),
child: Image.asset(
"assets/images/icons/butterlike.png",
color: Colors.grey.shade800,
),
),
),
Tab(
icon: Padding(
padding: EdgeInsets.all(6.0),
child: Image.asset(
"assets/images/icons/butterlike.png",
color: Colors.grey.shade800,
),
),
),
Tab(
icon: Padding(
padding: EdgeInsets.all(6.0),
child: Image.asset(
"assets/images/icons/butterlike.png",
color: Colors.grey.shade800,
),
),
),
],
),
TabBarView(
children: <Widget>[
Container(
color: Colors.grey,
),
Container(
color: Colors.green,
),
Container(
color: Colors.purple,
),
],
),
],
),
)
],
),
);
}
I did try a variation of this but couldn't get it to work.
The error description is clear, the TabBarView doesn't have a bounded height. the parent widget also doesn't have a bounded height. So, the Expanded widget will not solve this issue.
EDIT: below solutions are for above question(with columns).In general cases, use a ListView with shrinkWrap: true.(Or any other widgets with shrinkWrap) As #Konstantin Kozirev mentioned correctly, the shrinkWrap causes some performance issues. look for a better updated solution.
There are some options:
1st Solution:
Wrap the parent widget(Column) with a limited height widget like SizedBox or AspectRatio. Then use the Expanded widget like this:
Expanded(
child: TabBarView(...),
)
2nd Solution:
Use a bounded widget like SizedBox or AspectRatio on the TabBarView itself:
SizedBox(
height: 300.0,
child: TabBarView(...),
)
Note Your can also calcuate the height dynamicly if the height is not static.
I solved it by adding TabBar inside Container and TabBarView inside Expanded:
DefaultTabController(
length: 3,
child: Column(
children: <Widget>[
Container(child: TabBar(..)),
Expanded(child: TabBarView(..)),
],
),
);
try to use IndexedStack instead of TabBarView
i tried Expanded, shrinkWrap = true , ...
but no one work's fine
just try example.
Example:
class Product extends StatefulWidget {
#override
_ProductState createState() => _ProductState();
}
class _ProductState extends State<Product> with SingleTickerProviderStateMixin {
TabController tabController;
int selectedIndex = 0;
#override
void initState() {
super.initState();
tabController = TabController(length: 5, vsync: this, initialIndex: 0);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
initialIndex: 0,
child: Scaffold(
body: ListView(
shrinkWrap: true,
children: [
TabBar(
tabs: <Widget>[
Tab(
text: 'one',
),
Tab(
text: 'two',
),
Tab(
text: 'three',
),
],
controller: tabController,
onTap: (index) {
setState(() {
selectedIndex = index;
tabController.animateTo(index);
});
},
),
IndexedStack(
children: <Widget>[
Visibility(
child: Text('test1'),
maintainState: true,
visible: selectedIndex == 0,
),
Visibility(
child: Text('test2'),
maintainState: true,
visible: selectedIndex == 1,
),
Visibility(
child: Text('test3'),
maintainState: true,
visible: selectedIndex == 2,
),
],
index: selectedIndex,
),
],
),
),
);
}
}
special thank's to #arbalest
based on #Yamin answer I used SizeBox Like below to get full page
SizedBox.expand(
child: TabBarView(),
)
or any other size :
SizedBox(
height: height:MediaQuery.of(context).size.height // or every other size ,
child: TabBarView(),
)
The error message in console mentions this: "Viewports expand in the cross axis to fill their container and constrain their children to match their extent in the cross axis. In this case, a horizontal viewport was given an unlimited amount of vertical space in which to expand".
Clearly, the horizontal viewport here is referring to the TabBarView widget which is not given a height constraint.
So wrap both the TabBar and TabBarView widgets in Expanded widgets and give appropriate flex values to them to let them share their parent's height.
Concretely,
DefaultTabController(
length: 3,
child: Column(
children: <Widget>[
Expanded(
flex: 1,
child: TabBar(
tabs: <Widget>[
Tab(
icon: Padding(
padding: EdgeInsets.all(6.0),
child: Image.asset(
"assets/images/icons/butterlike.png",
color: Colors.grey.shade800,
),
),
),
Tab(
icon: Padding(
padding: EdgeInsets.all(6.0),
child: Image.asset(
"assets/images/icons/butterlike.png",
color: Colors.grey.shade800,
),
),
),
Tab(
icon: Padding(
padding: EdgeInsets.all(6.0),
child: Image.asset(
"assets/images/icons/butterlike.png",
color: Colors.grey.shade800,
),
),
),
],
),
),
Expanded(
flex: 9,
child: TabBarView(
children: <Widget>[
Container(
color: Colors.grey,
),
Container(
color: Colors.green,
),
Container(
color: Colors.purple,
),
],
),
)
],
),
)