How do I create curved bottom navigation bar in flutter - flutter

In my ongoing project, I need a curved bottom navigation bar. I have tried with curved_navigation_bar package. The result I got that's matched 80% of my requirements. The problem I have facing I can't make the curved position transparent.
This is the picture what I'm getting
This is the picture that I need
Here I've attached two pictures, First picture is what I tried myself I indicate the curved position that I need to make transparent and want to see the bottom list view like the second attached picture. Can anyone help me to reach to requirements.
my code:
import 'package:flutter/material.dart';
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: BottomNavBar(),
);
}
}
class BottomNavBar extends StatefulWidget {
const BottomNavBar({Key? key}) : super(key: key);
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _page = 0;
final GlobalKey<CurvedNavigationBarState> _bottomNavigationKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
key: _bottomNavigationKey,
index: 0,
height: 60.0,
items: <Widget>[
bottomItem(
title: "Wish List", index: 0, icon: Icons.favorite_border),
bottomItem(title: "Home", index: 1, icon: Icons.home),
bottomItem(title: "My Cart", index: 2, icon: Icons.shopping_cart),
],
color: Colors.black,
buttonBackgroundColor: Colors.white,
backgroundColor: Colors.blue,
animationCurve: Curves.easeInOut,
animationDuration: const Duration(milliseconds: 600),
onTap: (index) {
setState(() {
_page = index;
});
},
letIndexChange: (index) => true,
),
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 150,
color: Colors.primaries[index % Colors.primaries.length],
child: FittedBox(
child: Text(index.toString()),
),
);
}));
}
Widget bottomItem(
{required int index, required String title, required IconData icon}) {
if (index == _page) {
return Icon(
icon,
size: 26,
color: Colors.black,
);
} else {
return Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 22,
color: Colors.white,
),
const SizedBox(height: 5),
Text(
title,
style: const TextStyle(color: Colors.white),
)
],
),
);
}
}
}

Make these two changes to your code:
Change the background of your CurvedNavigationBar
backgroundColor: Colors.transparent
Add this to Your Scaffold
extendBody: true,

Does backgroundColor: Colors.transparent fit your usecase?

I have used curved navigation bar. You can make background transparent but there is no option to change curved angles and space between that animation area. You can try fluid_bottom_nav_bar package.

Related

Flutter: Increase hitbox of GestureDetector

I am fairly new to flutter and currently trying to create a NavBar.
It looks like this:
If I click on the icon, the bar moves to the selected one and the content changes.
However, I have to hit the icon perfectly. I would like to have a "box" around it, so I can tap just near it. Basically divide the space into 3.
I tried the following:
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
The items should take 1/3 of the width. It isn't working that way tho. Any idea on how to increase the "tappable" space?
EDIT
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.\
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var screens = [Text("Button1"), Text("Button2"), Text("Button3")];
int currentScreen = 0;
void changeIndex(int index) => setState(() {
currentScreen = index;
});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.orange,
child: Stack(
children: [
SafeArea(child: screens[currentScreen]),
Container(
alignment: Alignment.bottomCenter, child: NavBar(changeIndex))
],
),
),
);
}
}
class MenuItem {
final String name;
final Color color;
final double offset;
MenuItem(this.name, this.color, this.offset);
}
class NavBar extends StatefulWidget {
#override
State<StatefulWidget> createState() => NavBarState(navBarUpdate);
late Function(int) navBarUpdate;
NavBar(this.navBarUpdate);
}
class NavBarState extends State<NavBar> {
var items = [
MenuItem("Test", Colors.red, -0.76),
MenuItem("Test2", Colors.green, 0),
MenuItem("Test3", Colors.yellow, 0.76)
];
late MenuItem active;
late Function(MenuItem) navBarUpdate;
#override
void initState() {
super.initState();
active = items[0];
}
NavBarState(Function(int) navBarUpdate) {
this.navBarUpdate = (item) {
navBarUpdate(items.indexOf(item));
};
}
#override
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
}
You can use behavior: HitTestBehavior.translucent, or opaque on createNavBarItem
child: GestureDetector(
behavior: HitTestBehavior.translucent,
You can swap your GestureDetector on top level widget from Icon.
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return GestureDetector(
child: Container(
color: Colors.transparent,
width: width / items.length,
height: 55,
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
);
}

Toggle an animation between two separate Card classes in a Dialog with Flutter

In my Flutter application, I have a function that will open a dialog that shows two Stateful cards. I'm hoping to make it so that when one card is pressed, it will light up and the animation will run. Then, the other card will fade. However, in the current configuration, both options can be selected at once, which in a production setting might confuse the user. When the dialog opens, it should look like this:
Then the user should be able to select one or the other, and the buttons should toggle back and forth like so:
However, with the current way that my code is set up, the buttons could both be toggled at the same time, like this:
I haven't been able to figure out how to change the way that my code works to fit this. I've tried using Flutter's native ToggleButtons class, but I haven't been able to make it work to fit my needs in this project. Here's the code:
class CustomRoomStateCard extends StatefulWidget {
final bool isPublicCard; // true: card is green, false: card is red
static bool
choice; //true: user's room will be public, false: user's room will be private
CustomRoomStateCard({this.isPublicCard});
#override
_CustomRoomStateCardState createState() => _CustomRoomStateCardState();
}
class _CustomRoomStateCardState extends State<CustomRoomStateCard>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
#override
void initState() {
super.initState();
controller = AnimationController(
upperBound: 1,
duration: Duration(milliseconds: 200),
vsync: this,
);
animation = ColorTween(
begin: (widget.isPublicCard == true
? Colors.green[100]
: Colors.red[100]),
end: (widget.isPublicCard == true ? Colors.green : Colors.red))
.animate(controller);
controller.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.isPublicCard == true) {
CustomRoomStateCard.choice = true;
} else {
CustomRoomStateCard.choice = false;
}
if (animation.isCompleted) {
controller.reverse();
CustomRoomStateCard.choice = false;
print("choice is ${CustomRoomStateCard.choice}");
} else {
controller.forward();
print("choice is ${CustomRoomStateCard.choice}");
}
});
},
child: Card(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
color: animation.value,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(15.0),
child: widget.isPublicCard
? Icon(Icons.radar, color: Colors.white)
: Icon(Icons.shield, color: Colors.white),
),
Padding(
padding: EdgeInsets.all(15.0),
child: Text(
widget.isPublicCard ? "Public" : "Private",
style: kBoldText.copyWith(color: Colors.white),
textAlign: TextAlign.center,
))
],
),
));
}
}
Future<void> showPublicPrivateChoiceDialog(BuildContext context) {
List<bool> toggledValues = [false, false]; // an idea
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))),
title: Text(
"Set room privacy level",
style: TextStyle(fontWeight: FontWeight.bold),
),
content: Container(
height: MediaQuery.of(context).size.height * 0.2,
width: MediaQuery.of(context).size.height * 0.7,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: CustomRoomStateCard(
isPublicCard: true,
),
),
Expanded(
child: CustomRoomStateCard(
isPublicCard: false,
),
)
],
),
),
actions: [
TextButton(
onPressed: () {
print("the choice is ${CustomRoomStateCard.choice}");
isBroadcasting = CustomRoomStateCard.choice ??
true; // default to true in case they don't press anything
Navigator.pop(context);
return;
},
child: Text(
"Create",
style: TextStyle(fontWeight: FontWeight.bold),
))
],
);
});
}
My first thought would be to make a boolean variable that is true if one of the cards is already active. When I press a card, it would check this variable, change itself accordingly, but then would also have to call setState() in the other card, which I'm not sure how to do at the moment. How can I make it so these two cards will toggle back and forth and not be active at the same time? Any assistance would be greatly appreciated!
This depends on how much control you need over your animations. But if you don't need the controls, you can user AnimatedOpacity(..) to achieve this.
See this example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isPublic = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(
children: [
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: isPublic ? 1.0 : 0.20,
child: Card(
child: InkWell(
onTap: () {
setState(() {
isPublic = true;
});
print('is public = true');
},
child: SizedBox(
child: Text('Public'),
height: 120,
width: 120,
),
),
color: Colors.green[600],
),
),
SizedBox(height: 20),
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: !isPublic ? 1.0 : 0.20,
child: Card(
child: InkWell(
onTap: () {
setState(() {
isPublic = false;
});
print('is public = false');
},
child: SizedBox(
child: Text('Private'),
height: 120,
width: 120,
),
),
color: Colors.red[600],
),
),
],
)), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

Display BottomNavbar throughout the whole application instagram like

I have a homepage that includes an AppBar a bottomnavbar and a body to which I pass a list of widgets (pages) I want the navbar to navigate to when I click on its icons. I want to be able to display my bottomnavbar even in pages that are not included in the list .
Example when I click on a list tile it takes me to another details page , my navbar disappears I want the whole home page (appbar, bottomnavbar,...) to be static throughout the whole app without having to call each component on its own in my pages just like instagram style.
Here's my home page
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
var pages = [
MyQrqc(),
NoDataUI(),
];
int index = 0;
var _appPageController = PageController();
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: PreferredSize(
preferredSize: Size.fromHeight(70.0), child: CustomAppBar()),
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/bg.png"),
fit: BoxFit.cover,
),
),
child: PageView(
children: pages,
onPageChanged: (index) {
setState(() {
index = index;
});
},
controller: _appPageController,
),
),
bottomNavigationBar: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(17.0),
topRight: Radius.circular(17.0),
),
child: BottomAppBar(
clipBehavior: Clip.antiAlias, //bottom navigation bar on scaffold
color: Colors.transparent,
shape: CircularNotchedRectangle(), //shape of notch
notchMargin:
5, //notche margin between floating button and bottom appbar
child: Mainmenu(currentIndex: index, appPageController: _appPageController,),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
backgroundColor: kPrimaryLightColor,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyQrqc()),
);
},
child: const Icon(Icons.add),
),
),
);
}
}
and this is my main menu page :
import 'package:deepnrise/Screens/qrqc/mes_qrqc_view.dart';
import 'package:deepnrise/constants/colors.dart';
import 'package:deepnrise/services/auth_services.dart';
import 'package:deepnrise/utils/size_config.dart';
import 'package:flutter/material.dart';
import 'package:deepnrise/services/settings/settings_service.dart';
import 'package:flutter_svg/flutter_svg.dart';
class Mainmenu extends StatefulWidget {
int currentIndex = 0;
var appPageController = PageController();
Mainmenu({Key? key, required this.currentIndex,required this.appPageController}) : super(key: key);
#override
CustomNavBar createState() => CustomNavBar();
}
class CustomNavBar extends State<Mainmenu> {
#override
Widget build(BuildContext context) {
setBottomBarIndex(index) {
setState(() {
widget.currentIndex = index;
});
widget.appPageController.animateToPage(index,
duration: Duration(milliseconds: 500), curve: Curves.ease);
}
SizeConfig.init(context);
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [kPrimaryColor, kPrimaryLightColor],
begin: Alignment.topLeft,
end: Alignment.topRight,
stops: [0.1, 0.8],
tileMode: TileMode.clamp,
),
),
child: Wrap(
//children inside bottom appbar
alignment: WrapAlignment.spaceAround,
children: <Widget>[
const SizedBox(
width: 30,
),
IconButton(
icon: Image.asset("assets/icons/menuIcon2.png"),
onPressed: () {
setBottomBarIndex(0);
},
),
const SizedBox(
width: 20,
),
IconButton(
icon: Image.asset("assets/icons/menuIcon1.png"),
onPressed: () {
setBottomBarIndex(1);
},
),
const SizedBox(
width: 50,
),
IconButton(
icon: const Icon(
Icons.person,
color: Colors.white,
),
onPressed: () {
setBottomBarIndex(2);
},
),
const SizedBox(
width: 20,
),
IconButton(
icon: Image.asset("assets/icons/menuIcon3.png"),
onPressed: () {
setBottomBarIndex(3);
},
),
const SizedBox(
width: 20,
),
],
),
);
}
}
Here, First you need to create one file for BottomNavigationBar,
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
//For changing the screen
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
//This is a screens list which you want to navigate through BottomNavigationBar
final List<Widget> _children = [
const HomeScreen(),
const ProfileScreen()
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
iconSize: 32.0,
showSelectedLabels: true,
showUnselectedLabels: true,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white54,
currentIndex: _selectedIndex,
backgroundColor: Colors.black,
type: BottomNavigationBarType.fixed,
items: const [
BottomNavigationBarItem(
label: "Home",
icon: Icon(Icons.list),
),
BottomNavigationBarItem(
label: "Profile",
icon: Icon(Icons.person),
),
],
onTap: _onItemTapped,
),
);
}
}
Then, You can create another screens like HomeScreen, ProfileScreen etc.
So, By using this code HomePage to be static throughout the whole app without having to call each component in any screen.
To show the bottom navbar on all screens, one way is to use PageView with the bottom navbar. Another way is to use persistent_bottom_nav_bar

Is there a Flutter equivalent to Bootstrap Scrollspy?

I am looking for a flutter package that is equivalent to that of Bootstrap’s Scrollspy:
https://getbootstrap.com/docs/4.0/components/scrollspy/
The intended functionality is to have a vertical scrollable list of items with a sticky horizontal scrollable “header/navbar menu” on top of it. When the user scrolls through the vertical list and reaches a new “section” this is reflected in the horizontal navbar by highlighting the “section name” in the navbar and scrolling to it if necessary. When the user presses on a section name in the horizontal navbar, it should scroll to the start of that section in the vertical list.
Ex:
Section1 !!!Section2!!! Section3 Section4
——————————————————————
(Section1 is not visible)
!!!Section2!!!
Item3
Item4
Section3
Item1
Item2
Section4
Item5
Item6
I think you can achieve this with the scrollable_positioned_list package made by Google Fuchsia Authors.
The ScrollablePositionedList provides a ItemPositionsListener:
_itemPositionsListener.itemPositions.addListener(() {
final positions = _itemPositionsListener.itemPositions.value;
setState(() {
_topItem = positions.isNotEmpty ? positions.first.index : null;
});
});
Full source code
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _nbItems = 6;
final _itemHeight = 200.0;
final _itemPositionsListener = ItemPositionsListener.create();
int _topItem = 0;
#override
void initState() {
super.initState();
_itemPositionsListener.itemPositions.addListener(() {
final positions = _itemPositionsListener.itemPositions.value;
setState(() {
_topItem = positions.isNotEmpty ? positions.first.index : null;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: List.generate(
_nbItems,
(index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
padding: EdgeInsets.all(4.0),
decoration: _topItem == index
? BoxDecoration(
color: Colors.black26,
border: Border.all(color: Colors.black54),
)
: BoxDecoration(),
child: Text(
'S$index',
style: TextStyle(
fontWeight: _topItem == index
? FontWeight.bold
: FontWeight.normal,
),
),
),
),
),
),
Expanded(
child: ScrollablePositionedList.builder(
itemCount: _nbItems,
itemBuilder: (context, index) => SizedBox(
height: _itemHeight,
child: Card(
child: Text('Item $index'),
),
),
itemPositionsListener: _itemPositionsListener,
),
),
],
),
);
}
}

Unscheduled appbar

I’m having trouble finding information to get my appbar up and running, can you help me?
When I click another icon the screen stays the same!
import 'package:flutter/material.dart';
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
void main() => runApp(MaterialApp(home: BottomNavBar()));
class BottomNavBar extends StatefulWidget {
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
bool selected = false;
class _BottomNavBarState extends State<BottomNavBar> {
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
index: 0,
height: 50.0,
items: <Widget>[
Icon(Icons.home, size: 30, color: Colors.white),
Icon(Icons.camera, size: 30, color: Colors.white),
Icon(Icons.remove_red_eye, size: 30, color: Colors.white),
],
color: selected ? Colors.green.withOpacity(0.90) : Colors.red.withOpacity(0.90),
buttonBackgroundColor: selected ? Colors.green.withOpacity(0.90) : Colors.red.withOpacity(0.90),
backgroundColor: Colors.black12,
animationCurve: Curves.easeInOut,
animationDuration: Duration(
milliseconds: 550),
),
Next time post some code instead of screenshot of code but it seems to me that you are missing the onTap parameter and the widget call.
try adapting your code to this small example :
class _BottomNavBarState extends State<BottomNavBar> {
int _page = 0;
Widget myWidgets = [WidgetA(),WidgetB()];
GlobalKey _bottomNavigationKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
key: _bottomNavigationKey,
index: 0,
height: 50.0,
items: <Widget>[
Icon(Icons.add, size: 30),
Icon(Icons.list, size: 30),
],
color: Colors.white,
buttonBackgroundColor: Colors.white,
backgroundColor: Colors.blueAccent,
animationCurve: Curves.easeInOut,
animationDuration: Duration(milliseconds: 600),
onTap: (index) {
setState(() {
_page = index;
});
},
),
body: myWidgets[_page];
}
}