I'm trying to implement clickable the selected item in the Bottom navigation bar in my Flutter app. What I'm trying to achieve is when the user clicks any item in the Bottom navigation bar the selected item page contains the button which navigates to another inner page so if I want to try to click the select item in the Bottom navigation bar it shows me the same inner page, the app changes the selected tab inside the bottom navigation bar. but if I click again the select item tab it shows me the same inner page. Any help is appreciated.
Or Maybe it's clickable but the selected tab shows me the inner page only
Any help is appreciated.
My main.dart:-
import 'package:flutter/material.dart';
import 'MyPage.dart';
import 'MyPage2.dart';
import 'MyPage3.dart';
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'Notifications.dart';
import 'MyCustomPage.dart';
class MyApp extends StatefulWidget {
#override
_MyAppcreateState() => _MyApp();
}
class _MyApp extends State<MyApp> {
late List<Widget> _pages;
List<BottomNavigationBarItem> _items = [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.messenger_rounded),
label: "Messages",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: "Settings",
)
];
late int _selectedPage;
#override
void initState() {
super.initState();
_selectedPage = 0;
_pages = [
MyPage(
count: 1,
),
MyPage2(
count: 2,
),
MyPage3(
count: 3,
),
];
}
#override
Widget build(BuildContext context) {
print(_selectedPage);
return Scaffold(
body: _pages[_selectedPage],
bottomNavigationBar: BottomNavigationBar(
items: _items,
currentIndex: _selectedPage,
onTap: (index) {
setState(() {
_selectedPage = index;
});
},
)
);
}
}
MyPage.dart
import 'package:flutter/material.dart';
import 'MyCustomPage.dart';
import 'Notifications.dart';
class MyPage extends StatefulWidget {
final count;
MyPage({Key? key, this.count}) : super(key: key);
#override
_MyPage createState() => _MyPage();
}
class _MyPage extends State<MyPage>{
late int _selectedPage;
#override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('page 01'),
),
body: Center(
child: RaisedButton(
child: Text('my page1'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => MyCustomPage()
)
);
},
),
),
);
},
);
},
);
}
}
MyCustomPage.dart
import 'package:flutter/material.dart';
class MyCustomPage extends StatefulWidget {
MyCustomPage({Key? key}) : super(key: key);
#override
_MyCustomPage createState() => _MyCustomPage();
}
class _MyCustomPage extends State<MyCustomPage>{
#override
Widget build(BuildContext parentContext) {
return Scaffold(
appBar: AppBar(
title: Text('custompage'),
),
body: Column(
children: [
Expanded(
child: Container(
child: ListView.builder(
itemCount: 15,
itemBuilder: (context, index) {
return Container(
width: double.infinity,
child: Card(
child: Center(
child: Text('My Custom Page'),
),
),
);
},
),
),
),
],
),
);
}
}
I add the image for a better understanding:-
my home page view
MycustomPage/inner page view
this is my issue what I want is when I navigate to the inner page the select tab must be unselectable and when I click on that selected tab it will show the first page which is MyPage.dart page, not the inner page(MyCustomPage.dart).
Please answer me if any further questions.
Please comment to me if you don't understand it.
But please don't ignore this. I really want to do that task.
Any help is appreciated.
Till I understood this thing is not possible with default BottomNavigationBar
maybe you can with custom. this is already a predefined index in the BottomNavigationBar constructor and set it to "0"
int currentIndex = 0,
BottomNavigationBar(
{Key? key,
required List<BottomNavigationBarItem> items,
ValueChanged<int>? onTap,
int currentIndex = 0,
double? elevation,
BottomNavigationBarType? type,
Color? fixedColor,
Color? backgroundColor,
double iconSize = 24.0,
Color? selectedItemColor,
Color? unselectedItemColor,
IconThemeData? selectedIconTheme,
IconThemeData? unselectedIconTheme,
double selectedFontSize = 14.0,
double unselectedFontSize = 12.0,
TextStyle? selectedLabelStyle,
TextStyle? unselectedLabelStyle,
bool? showSelectedLabels,
bool? showUnselectedLabels,
MouseCursor? mouseCursor,
bool? enableFeedback,
BottomNavigationBarLandscapeLayout? landscapeLayout}
)
BottomNavigationBar ref
Related
I have 4 pages in a GNav() Bottom-Navigation-Bar and I would like to navigate to different page via click of a button.
I am aware that there might be similar questions on stackoverflow already, however I was unable to implement any of them successfully which is why I am posting here as my "last effort".
First off, my Class RootPage() which is where I am defining my GNav bar and the pages linked to each tab.
import 'package:anitan/pages/menu_bar_pages/profile_page.dart';
import 'package:anitan/pages/menu_bar_pages/search_page.dart';
import 'package:anitan/pages/menu_bar_pages/settings_pages/settings_page.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'home_page.dart';
class RootPage extends StatefulWidget {
RootPage({super.key});
#override
State<RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<RootPage>{
int currentPage = 0;
List<Widget> pages = const [
HomePage(),
ProfilePage(),
SearchPage(),
SettingsPage()
];
final user = FirebaseAuth.instance.currentUser!;
#override
Widget build(BuildContext context) {
return Scaffold(
body: pages[currentPage],
bottomNavigationBar: Container(
color: Colors.green,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 15.0, vertical: 15),
child: GNav(
backgroundColor: Colors.green,
color: Colors.white,
activeColor: Colors.white,
tabBackgroundColor: Colors.green.shade800,
gap: 8,
onTabChange: (index) => setState(() => currentPage = index),
selectedIndex: currentPage,
padding: const EdgeInsets.all(16),
tabs: const [
GButton(icon: Icons.home, text: "Home"),
GButton(icon: Icons.person, text: "My Page"),
GButton(icon: Icons.search, text: "Browse"),
GButton(icon: Icons.settings, text: "Settings"),
],
),
),
),
);
}
}
Next up, the HomePage which has a button. When clicked, I want to change the selected tab of my GNav bar. Pushing the page etc. will lead to the Navigation Bar disappearing which is why this isn't an option.
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key,});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text("Home Page"),
automaticallyImplyLeading: false,
leading: IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.arrow_back_ios),
),
actions: [
IconButton(
onPressed: () {
debugPrint("appBar Button");
},
icon: const Icon(Icons.info_outline),
),
],
),
body: SafeArea(
child: ElevatedButton(onPressed: (() => print("Change Page Here")), child: Text("Change-Page")),
),
);
}
}
I tried a few things like implementing a StreamController or using a GlobalKey. But I would like to the GNav Bar instead of using a Tab controller because of its design.
In theory, what I am trying to do seems simple:
I would like to currentPage to a new index and call setState() to show the changes.
Can anyone help me understand how I can access change the index and update the selected page in my RootPage?
I have spent 2 days looking into various solutions but I can't get any of them to work.
Many thanks!
Edit:
Please find the code of my main.dart below.
import 'package:anitan/pages/menu_bar_pages/root_page.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
//import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: RootPage(),
);
}
}
HomePage is already a part of RootPage which is needed to be on the same route.You can use callback method to handle click-event/changing page.
class HomePage extends StatefulWidget {
final VoidCallback onTap;
const HomePage({
super.key,
required this.onTap,
});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child:
ElevatedButton(onPressed: widget.onTap, child: Text("Change-Page")),
),
);
}
}
Now you will get a callback method while creating HomePage
int currentPage = 0;
List<Widget> pages = [
HomePage(onTap:(){
// page
} ),
];
Given the reply from Yeasin Sheikh I implemented a little workaround which seems to be working fine:
First, as recommended by Yeasin, I added a VoidCallback function onTap to my HomePage class:
class HomePage extends StatefulWidget {
final VoidCallback onTap;
const HomePage({super.key, required this.onTap});
#override
State<HomePage> createState() => _HomePageState();
}
Then I called this onTap function clicking the button:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child:
ElevatedButton(onPressed: widget.onTap(), child: Text("Change-Page")),
),
);
}
}
In my rootPage class, I then created my HomePage.
When the onTap function of that page is being called, I am calling a method to refresh the page (setState) and changing the currentPage index as part of it.
This required me to initialize the List of my pages using "late".
class _RootPageState extends State<RootPage>{
int currentPage = 0;
late List<Widget> pages = [
HomePage(onTap:(){
_changePage(2);
} ),
const ProfilePage(),
const SearchPage(),
const SettingsPage()
];
_changePage(int id){
setState(() {
currentPage = id;
});
}
To keep everything centralized, I switched to using the same method to update the index in the onTabChange part of the GNav bar as well.
onTabChange: (index) => setState(() => _changePage(index)),
Clicking the button now switches the page.
The bottom navigation bar (GNav) will stay on the screen and can be used as usual.
Thank you for your support in figuring this out!
I'm trying to add "quick search buttons" to my flutter app like (in red):
I'm not sure if this is a material design component - if it is, I was hoping to identify the name so that I can e.g. search https://pub.dev/ for an existing widget.
Does anyone know what this material design component is called (if applicable)?
How can I implement it/does an existing widget exist on e.g. https://pub.dev/?
Many thanks! :)
you can use FilterChip widget for implementing "quick search buttons"
https://api.flutter.dev/flutter/material/FilterChip-class.html
or you can also create your custom widget for achieving this UI
import 'package:flutter/material.dart';
class MyFilterChip extends StatefulWidget {
const MyFilterChip({Key? key}) : super(key: key);
#override
State<MyFilterChip> createState() => _MyFilterChipState();
}
class _MyFilterChipState extends State<MyFilterChip> {
List fruits = ['apple', 'banana', 'mango', 'papaya', 'orange', 'guava'];
int selectedIndex = -1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(15),
child: SizedBox(
height: 30,
child: ListView.separated(
itemCount: fruits.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) => FilterChip(
label: Text(fruits[index]),
selected: index == selectedIndex,
onSelected: (value) {
setState(() {
selectedIndex = index;
});
},
),
separatorBuilder: (BuildContext context, int index) =>
const SizedBox(
width: 10,
),
),
),
),
],
),
);
}
}
Those are called chips, refer to this page https://material.io/components/chips. There is also a section of how to implement these in flutter.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
gif1: https://drive.google.com/file/d/1ldqHqicHFEauUQbz1fzH5K8XRS3ICNqL/view?usp=sharing
gif2: https://drive.google.com/file/d/1NpPVrO9trMbde-rPmbAGeHRVSh28BfPk/view?usp=sharing
i'm making an app that using map and bottomnavigation bar and sliding-up panel.
what my problem is that like in the first gif I want to make bottom navigation bar and sliding up panel(or draggable scrollable sheet). when I drag the whole panel down it disappear and when I tap the icon on the navigation bar, it appears again
but in the second gif which I made does not
it does not disappear even if dragging the whole panel down. well I can adjust the minimum height of the panel to make the panel disappear but there's no way to pop up the panel when I tap the button again
I used this package https://pub.dev/packages/sliding_up_panel
I think there is not an option that i want in this package(could be wrong! please notify me if I'm wrong)
so my question is that is there a flutter package that can implement the function that I mentioned above?
Try this code, and see if it helpful to you
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
_State createState() => _State();
}
class _State extends State<HomePage> {
int _selectedIndex = 0;
final double _sheetSize = 0.4;
_onItemTapped(index) {
setState(() {
DraggableScrollableActuator.reset(context);
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: SizedBox.expand(
child: DraggableScrollableSheet(
key: UniqueKey(),
initialChildSize: _sheetSize,
minChildSize: 0.0,
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[100],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
Im building a list from Firestore collection stream using ListViewBuilder, each item is a Text widget in the list, Im trying to change color of the text onTap the Text widget,
Item 1
Item 2
Item 3
when touching/onTap Item1, text color of Item1 should be changed
I implemented using GestureDetector with setState but on onTap>setState execution, the listview of the stream is rebuilt, giving a second of blank screen/flicker and loosing the actual state since its refreshed
var _dynamicTextColor = Colors.green;
return ListView.builder(
reverse: true,
itemCount: itemStream.length,
itemBuilder: (context, itemIndex) => Container(
child:GestureDetector(
onTap: (){
setState(() {
_dynamicTextColor = Colors.white;
});
},
child: Text(itemStream[itemIndex]['title'], style: TextStyle(color:_dynamicTextColor),),
),
),
);
You can try the following. This will use the selected property to decide which container should be blue.
class _TestState extends State<Test> {
String selected = "first";
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
selected = 'first';
});
},
child: Container(
height: 200,
width: 200,
color: selected == 'first' ? Colors.blue : Colors.transparent,
child: Text("First"),
),
),
GestureDetector(
onTap: () {
setState(() {
selected = 'second';
});
},
child: Container(
height: 200,
width: 200,
color: selected == 'second' ? Colors.blue : Colors.transparent,
child: Text("Second"),
),
),
],
);
}
}
I recommend you use a state management, for example Provider.
I prepared an example, I hope you understand it and solve your problem.
First, add the provider package to pubspec.yaml
provider: ^4.3.3
Then, create a class where you will manage the states, as you would with setState. The code would be like this:
class ColorProvider extends ChangeNotifier {
ColorProvider();
bool isPressed = true;
Color color = Colors.black;
changeColor() {
if (isPressed == true) {
color = Colors.green;
isPressed = false;
} else {
color = Colors.black;
isPressed = true;
}
notifyListeners();
return isPressed;
}
}
Once this is done, you must Wrap the App material in a ChangeNotifierProvider widget:
import 'package:flutter/material.dart';
import 'package:flutter_application_1/home_page.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ColorProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(),
),
);
}
}
And finally, call the Provider in the widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final providerWatch = context.watch<ColorProvider>();
final providerRead = context.read<ColorProvider>();
String text = 'This is an example text';
return Scaffold(
body: Container(
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => providerRead.changeColor(),
child: Text(
text,
style: TextStyle(color: providerWatch.color),
)),
),
));
}
}
and this would be the result:
Basically, I have a main widget that has a this._bottomNavigationBar and when the screen will change, I pass it to the new screen. Then, each screen has its own Scaffold and AppBar.
Sounds perfectly, but it's not working properly.
When I click to change the selected option, it changes the screen, but the selected icon keeps the same. If I print the this.currentIndex, it changes the index normally. Something that I was not expecting to happen, because when I declare the this._bottomNavigationBar, the currentIndex property is defined like that: currentIndex: this.currentIndex, so if the icon isn't changing, then the variable won't either.
So, I realized that I probably forgive to use the setState() method on the onTap, but it wasn't the case.
I just don't know why this is happening. I think it's something about the setState "scope", maybe? I'll leave my code below to illustrate this better.
home.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'landing.dart';
const List<BottomNavigationBarItem> homeScreenNavbarItems = [
BottomNavigationBarItem(
icon: Icon(FontAwesomeIcons.home),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.bookmark),
title: Text(''),
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text(''),
),
];
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int currentIndex = 1;
BottomNavigationBar _bottomNavigationBar;
List homeScreens;
void onNavbarTapped(int index) {
setState(() {
this.currentIndex = index;
});
}
#override
void initState() {
this._bottomNavigationBar = BottomNavigationBar(
showSelectedLabels: false,
showUnselectedLabels: false,
selectedItemColor: Color(0xFF000000),
unselectedItemColor: Color(0xFFb3b3b3),
currentIndex: this.currentIndex,
items: homeScreenNavbarItems,
onTap: this.onNavbarTapped,
);
this.homeScreens = [
LandingScreen(this._bottomNavigationBar),
Scaffold(
bottomNavigationBar: this._bottomNavigationBar,
)
];
super.initState();
}
#override
Widget build(BuildContext context) {
print(this.currentIndex);
return this.homeScreens.elementAt(this.currentIndex);
}
}
landing.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class LandingScreen extends StatefulWidget {
final BottomNavigationBar _bottomNavigationBar;
LandingScreen(this._bottomNavigationBar);
#override
State<StatefulWidget> createState() {
return _LandingScreenState();
}
}
class _LandingScreenState extends State<LandingScreen> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('a'),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
bottomNavigationBar: widget._bottomNavigationBar,
),
);
}
}
The main.dart file is simply calling the Home widget, so I didn't place it here.
Edit
I removed all the content of the initState method and placed then into the Widget build method, worked, but I don't know exactly why...
The issue here is that the BottomNavigationBar is configured in initState. When currentIndex has been updated after calling setState, the properties aren't updated because it's inside initState instead of Widget build.