I am building an online bottle store app using flutter and I am having an issue where if I add a product to favorites the selected product's button won't stay selected on the home page if I switch pages. I have categorized the products using a Tabbar and Tabbarview. I have tried using AutomaticKeepAliveClientMxin to keep the page alive but with no success. Please can anyone assist.
Here's what happens:
I click on the selected product
then it is added to Favorites
Come back to the home page and the selected item is no longer showing that it is selected
Here's my code:
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
ProductProvider productProvider = ProductProvider();
late TabController tabController;
#override
void initState() {
super.initState();
tabController = TabController(length: 4, vsync: this);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
var cart = Provider.of<ShoppingCartProvider>(context);
var favoriteProvider = Provider.of<FavoriteProvider>(context);
Size _screenSize = MediaQuery.of(context).size;
final double itemHeight = (_screenSize.height - kToolbarHeight - 24) / 2;
final double itemWidth = _screenSize.width / 2;
return Scaffold(
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Categories',
style: TextStyle(
fontSize: 20.0,
fontFamily: 'Montserrat-ExtraBold',
fontWeight: FontWeight.bold),
),
),
Container(
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
controller: tabController,
indicator:
CircleTabIndicator(color: Colors.redAccent, radius: 4.0),
isScrollable: true,
labelColor: Colors.redAccent,
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 20.0),
unselectedLabelColor: Colors.black,
unselectedLabelStyle: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 20.0),
tabs: const [
Tab(text: 'Brandy'),
Tab(text: 'Gin'),
Tab(text: 'Soft drinks'),
Tab(text: 'Whiskey')
],
),
),
),
Container(
height: 400,
width: double.maxFinite,
child: TabBarView(
controller: tabController,
children: productProvider.categories.map((bottleCategory) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: itemWidth / itemHeight,
),
itemCount: bottleCategory.bottleList.length,
itemBuilder: (context, index) {
return Card(
shadowColor: Colors.grey,
surfaceTintColor: Colors.amber,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
child: Stack(
children: [
Positioned(
right: 0,
child: InkWell(
onTap: () {
favoriteProvider.toggleFavorites(
bottleCategory.bottleList[index]);
if (favoriteProvider.isExist(
bottleCategory.bottleList[index])) {
ScaffoldMessenger.of(context)
.hideCurrentSnackBar();
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(
content: Text(
"Product Added to Favorite!",
style: TextStyle(fontSize: 16),
),
backgroundColor: Colors.green,
duration: Duration(seconds: 1),
),
);
} else {
ScaffoldMessenger.of(context)
.hideCurrentSnackBar();
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(
content: Text(
"Product Removed from Favorite!",
style: TextStyle(fontSize: 16),
),
backgroundColor: Colors.red,
duration: Duration(seconds: 1),
),
);
}
},
child: favoriteProvider.isExist(
bottleCategory.bottleList[index])
? const Icon(
Icons.favorite,
color: Colors.redAccent,
)
: const Icon(Icons.favorite_border),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Image.asset(
bottleCategory.bottleList[index].image,
height: 200.0,
),
),
Center(
child: Text(
bottleCategory
.bottleList[index].bottleName,
style: const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold))),
Center(
child: Text(
'R${bottleCategory.bottleList[index].price}'),
)
],
),
Positioned(
bottom: 0,
right: 10,
child: IconButton(
icon: const Icon(Icons.add_circle),
iconSize: 40.0,
onPressed: () {
cart.addToCart(
bottleCategory.bottleList[index].id,
bottleCategory
.bottleList[index].bottleName,
bottleCategory
.bottleList[index].price,
bottleCategory
.bottleList[index].image);
},
))
],
),
);
},
);
}).toList()),
),
],
),
),
);
}
}
class FavoriteProvider with ChangeNotifier {
List<Bottle> _favItems = [];
List<Bottle> get favItems {
return [..._favItems];
}
void toggleFavorites(Bottle favBottle) {
final isExist = _favItems.contains(favBottle);
if (isExist) {
_favItems.remove(favBottle);
} else {
_favItems.add(favBottle);
}
notifyListeners();
}
bool isExist(Bottle favBottle) {
final isExist = _favItems.contains(favBottle);
return isExist;
}
void clearFavorite() {
_favItems = [];
notifyListeners();
}
}
Try using Consumer widget. Like so:
Consumer<favoriteProvider>(
builder: (BuildContext context, favorite, _){
return Icon(
Icons.favorite,
color: favorite.isExist(bottleCategory.bottleList[index])? Colors.redAccent : null,
);
},
),
Consumer widget will refresh or change the state whenever the ChangeNotifier of that model, in this case, FavoriteProvider is triggered, this should allows your widget to change and check itself anytime. So you shouldn't need to keep your state or screen alive all the time.
If that doesn't work, please change your Business Logic in the FavoriteProvider. Instead of using contains, I suggest to use any and identifies each instances with its own id or any of its unique variable. Like so:
bool isExist(Bottle favBottle) {
final isExist = _favItems.any((e) =>e.bottleName == favBottle.bottleName);
return isExist;
}
Related
I'm making a Pomodoro app and I don't know how to implement Get and Obx if the timer is over and make that the tab bar change automatically.
This is my code:
Tab bar:
#override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: AnimatedBuilder(
animation: _countDownController.controller,
builder: (context, child) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(22),
child: Container(
color: Colors.transparent,
child: SafeArea(
child: ResponsiveWeb(
child: Column(children: [
TabBar(
controller: _tabController,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(
color: Color(0xff3B3B3B), width: 2.0),
insets: EdgeInsets.fromLTRB(
12.0, 12.0, 12.0, 12.0)),
indicatorWeight: 5,
indicatorSize: TabBarIndicatorSize.label,
labelColor: const Color(0xff3B3B3B),
labelStyle: GoogleFonts.nunito(
fontSize: 16.0,
// letterSpacing: 1,
fontWeight: FontWeight.w500),
unselectedLabelColor: const Color(0xffD7D7D7),
tabs: const [
Tab(
text: "Pomodoro",
icon: Icon(Icons.work_history_outlined,
size: 24),
),
Tab(
text: "Short break",
icon: Icon(Icons.ramen_dining_outlined,
size: 24),
),
Tab(
text: "Long break",
icon: Icon(
Icons.battery_charging_full_outlined,
size: 24),
),
]),
]),
),
),
),
),
),
),
);
},
),
);
}
}
And my timer:
This is an example from an animation which means that if the timer starts the animation starts as well
createAnimationController(TickerProvider ticker) {
currentRoundType = typeRound.pomodoro;
_changeCurrentRoundTypeString();
tickerProvider = ticker;
currentRoundSeconds.value = currentRoundType == typeRound.pomodoro
? _settingsController.secondsWork.value
: currentRoundNumber.value < _settingsController.rounds.value
? _settingsController.secondsBreak.value
: _settingsController.secondsBreakAfterRound.value;
restartTimers();
controller = AnimationController(
vsync: tickerProvider,
duration: currentDuration,
);
logger.d(controller.value);
painter = CustomTimePainter(
backgroundColor: const Color.fromARGB(0, 33, 149, 243),
color: const Color(0xffD94530),
animation: controller,
);
timerString.value =
'${(currentDuration.inHours).toString().padLeft(2, '0')}:${(currentDuration.inMinutes % 60).toString().padLeft(2, '0')}:${(currentDuration.inSeconds % 60).toString().padLeft(2, '0')}';
listRounds.value = List.generate(
_settingsController.rounds.value + 1, (index) => Rx(stateRound.undone));
}
This is another example, which means that if the timer ends the empty image is colored with red color
import 'package:flutter/material.dart';
import 'package:get/get.dart%20';
import 'package:pomodoro/3.tomatoes_interval_UI/countdown_controller.dart';
import 'package:pomodoro/3.tomatoes_interval_UI/tomato_icon.dart';
class TomatoesIcons extends StatefulWidget {
const TomatoesIcons({super.key});
#override
State<TomatoesIcons> createState() => _TomatoesIconsState();
}
class _TomatoesIconsState extends State<TomatoesIcons>
with TickerProviderStateMixin {
final CountDownController _countDownController = Get.find();
final ScrollController _horizontal = ScrollController();
#override
void initState() {
super.initState();
_countDownController.createAnimationController(this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _countDownController.controller,
builder: (context, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: const Color.fromARGB(255, 255, 202, 55),
height: 65,
width: MediaQuery.of(context).size.width,
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Obx(
() => Scrollbar(
controller: _horizontal,
child: SingleChildScrollView(
controller: _horizontal,
scrollDirection: Axis.horizontal,
child: Row(
children: _countDownController.listRounds
.map(
(e) => MouseRegion(
cursor: SystemMouseCursors.click,
child: TomatoIcon(e),
),
)
.toList(),
),
),
),
),
),
),
),
],
);
},
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pomodoro/3.tomatoes_interval_UI/countdown_controller.dart';
class TomatoIcon extends StatefulWidget {
final Rx<stateRound> state;
const TomatoIcon(this.state, {Key? key}) : super(key: key);
#override
State<TomatoIcon> createState() => _TomatoIconState();
}
class _TomatoIconState extends State<TomatoIcon> {
#override
Widget build(BuildContext context) {
return Obx(
() => IconButton(
onPressed: null,
icon: widget.state.value == stateRound.done
? Image.asset('assets/icons/tomatoDone.png')
: Image.asset('assets/icons/tomatoUndone.png')),
);
}
}
With these examples, I would like to create a function or " if statement" which triggers the timer is over, select automatically a tab bar.
Thanks for any help you can provide
I have this problem that I want to make my FAQs UI like Original UI but i cant making like that.
I have this problem that I want to make my FAQs UI like Original UI but i cant making like that.
I want like this UI.
But its creating like this.
My UI is not matching please help me How to make it like original.
This is my code.
import 'package:cwc/constants/constants.dart';
import 'package:flutter/material.dart';
import '../skeleton.dart';
class FAQPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return FAQPageState();
}
}
class FAQPageState extends State<FAQPage> {
bool isExpand = false;
var selected;
#override
void initState() {
// TODO: implement initState
super.initState();
isExpand = false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffffff),
appBar: AppBar(
elevation: 0.5,
leading: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
color: Colors.black,
size: tSize24,
)),
centerTitle: true,
backgroundColor: Colors.white,
),
body: isFAQLoading == true
? ButtonSkeleton()
: ListView.builder(
itemCount: faqListData.length,
itemBuilder: (context, index) {
return Column(children: <Widget>[
Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey.shade200,
offset: Offset(1.0, 1.0),
spreadRadius: 0.2)
]),
child: Card(
elevation: 0,
shadowColor: Colors.grey,
margin: EdgeInsets.only(
bottom: 3,
),
child: ExpansionTile(
key: Key(index.toString()),
backgroundColor: Color(0xfff6f7f9),
initiallyExpanded: index == selected,
iconColor: Colors.grey,
title: Text(
'${faqListData[index]['question']}',
style: TextStyle(
color: Color(0xFF444444),
fontSize: tSize14,
fontWeight: FontWeight.w500),
),
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: 10.0, bottom: 10, left: 17, right: 17),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Text(
"${faqListData[index]['answer']}",
style: TextStyle(
color: Color(0xFF444444),
fontSize: 13,
),
textAlign: TextAlign.start,
),
),
],
))
],
onExpansionChanged: ((newState) {
isExpand = newState;
print(newState);
if (newState)
setState(() {
Duration(seconds: 20000);
selected = index;
// isExpand=newState;
});
else
setState(() {
selected = -1;
// isExpand=newState;
});
print(selected);
})),
),
),
]);
}),
);
}
}
Hi I checked your code and looks just small thing is missing. When you are passing key to the Exapnsion tile you need to pass same key to ListView.builder then it works fine.
ListView.builder(
key: Key('builder ${selected.toString()}'),
itemCount: faqListData.length,
...
...
);
i make admin page.
I am making an admin page. I want to change only the red screen on the right side when the left button is pressed. However, the url must be changed as well.
import 'package:flutter/material.dart';
class SplashPage extends StatefulWidget {
const SplashPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage>
with SingleTickerProviderStateMixin {
late TabController tabController;
#override
void initState() {
super.initState();
tabController = new TabController(length: 5, vsync: this);
tabController.addListener(() {
if (tabController.indexIsChanging) setState(() {});
});
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Scaffold(
backgroundColor: Colors.white,
body: Container(child: _portraitWidget(height, width)),
);
}
Widget _portraitWidget(double height, double width) {
return Container(
height: height,
width: width,
child: Row(
children: [
Container(
height: MediaQuery.of(context).size.height,
width: 100,
color: Colors.white,
alignment: AlignmentDirectional.center,
// padding: const EdgeInsets.only(bottom: 10),
child: RotatedBox(
quarterTurns: 1,
child: TabBar(
controller: tabController,
indicatorColor: Colors.transparent,
indicatorPadding: EdgeInsets.all(0.0),
// labelPadding: EdgeInsets.only(
// left: AppDimens.space40, right: AppDimens.space40),
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
tabs: <Widget>[
getItem(page: HomePagesEnum.PAGE_1, text: "PAGE_1"),
getItem(page: HomePagesEnum.PAGE_2, text: "PAGE_2"),
getItem(page: HomePagesEnum.PAGE_3, text: "PAGE_3"),
getItem(page: HomePagesEnum.PAGE_4, text: "PAGE_4"),
getItem(page: HomePagesEnum.PAGE_5, text: "PAGE_5"),
],
),
),
),
Expanded(
child: Container(
// padding: const EdgeInsetsDirectional.only(end: AppDimens.space32),
child: TabBarView(
controller: tabController,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
_buildContent("Page 1", Colors.red),
_buildContent("Page 2", Colors.blue),
_buildContent("Page 3", Colors.blueAccent),
_buildContent("Page 4", Colors.amber),
_buildContent("Page 5", Colors.green),
],
),
),
),
],
),
);
}
Widget _buildContent(String title, Color color) {
return Container(
child: Text("$title"),
color: color,
);
}
Widget getItem({required String text, required HomePagesEnum page}) {
bool isSelected = (page.index == tabController.index);
return RotatedBox(
quarterTurns: -1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
text,
style: TextStyle(
color: isSelected ? Colors.black : Colors.grey,
fontWeight: FontWeight.bold,
fontSize: isSelected ? 18 : 15),
),
],
),
);
}
}
enum HomePagesEnum { PAGE_1, PAGE_2, PAGE_3, PAGE_4, PAGE_5 }
enter image description hereAs per image I want popup in app-bar in flutter
Try the below code and you will store the SVG image in image directory
actions[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 5),
child: GestureDetector(
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(width: 2, color: Colors.blue)),
child: SvgPicture.asset(
"images/ic_more.svg",
height: 30,
color: Colors.white,
),
),
onTapDown: (details) {
_showPopUpMenu(details.globalPosition);
})
)
]
popUpMenu:
_showPopUpMenu(Offset offset) async {
final screenSize = MediaQuery.of(context).size;
double left = offset.dx;
double top = offset.dy;
double right = screenSize.width - offset.dx;
double bottom = screenSize.height - offset.dy;
await showMenu<MenuItemType>(
context: context,
position: RelativeRect.fromLTRB(left, top, right, bottom),
items: MenuItemType.values
.map((MenuItemType menuItemType) =>
PopupMenuItem<MenuItemType>(
value: menuItemType,
child: Text(getMenuItemString(menuItemType)),
))
.toList(),
).then((MenuItemType item) {
if (item == MenuItemType.EDIT) {
// here set your route
}
});
}
And your enum data for popup menu
import 'package:flutter/foundation.dart';
enum MenuItemType {
EDIT,
DUPLICATE
}
getMenuItemString(MenuItemType menuItemType) {
switch (menuItemType) {
case MenuItemType.EDIT:
return "Edit";
case MenuItemType.DUPLICATE:
return "Duplicate";
}
}
Please refer to below code
Using custom_pop_up_menu: ^1.2.2
https://pub.dev/packages/custom_pop_up_menu
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<ChatModel> messages;
List<ItemModel> menuItems;
CustomPopupMenuController _controller = CustomPopupMenuController();
#override
void initState() {
menuItems = [
ItemModel('Chat', Icons.chat_bubble),
ItemModel('Add', Icons.group_add),
ItemModel('View', Icons.settings_overscan),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CustomPopupMenu'),
actions: <Widget>[
CustomPopupMenu(
child: Container(
child: Icon(
Icons.more_horiz,
color: Colors.white,
size: 24.0,
),
padding: EdgeInsets.symmetric(
horizontal: 30.0,
vertical: 20.0,
),
),
menuBuilder: () => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
color: Colors.white,
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: menuItems
.map(
(item) => GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: _controller.hideMenu,
child: Container(
height: 40,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: <Widget>[
Icon(
item.icon,
size: 15,
color: Colors.black,
),
Expanded(
child: Container(
margin: EdgeInsets.only(left: 10),
padding:
EdgeInsets.symmetric(vertical: 10),
child: Text(
item.title,
style: TextStyle(
color: Colors.black,
fontSize: 12,
),
),
),
),
],
),
),
),
)
.toList(),
),
),
),
),
pressType: PressType.singleClick,
verticalMargin: -10,
controller: _controller,
barrierColor: Colors.black54,
horizontalMargin: 0.0,
arrowColor: Colors.white,
showArrow: true,
),
],
),
body: Container(
child: Center(
child: Text(
"Pop up menu",
),
),
),
);
}
}
Solution Using PopupmenuButton
Widget popMenus({
List<Map<String, dynamic>> options,
BuildContext context,
}) {
return PopupMenuButton(
iconSize: 24.0,
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
icon: Icon(
Icons.more_horiz_rounded,
color: Colors.black,
size: 24.0,
),
offset: Offset(0, 10),
itemBuilder: (BuildContext bc) {
return options
.map(
(selectedOption) => PopupMenuItem(
height: 12.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
selectedOption['menu'] ?? "",
style: TextStyle(
fontSize: ScreenUtil().setSp(14.0),
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal,
color: Colors.blue,
),
),
(options.length == (options.indexOf(selectedOption) + 1))
? SizedBox(
width: 0.0,
height: 0.0,
)
: Padding(
padding: EdgeInsets.symmetric(
vertical: 8.0,
),
child: Divider(
color: Colors.grey,
height: ScreenUtil().setHeight(1.0),
),
),
],
),
value: selectedOption,
),
)
.toList();
},
onSelected: (value) async {},
);
}
class PopUpmenusScreen extends StatefulWidget {
const PopUpmenusScreen({Key key}) : super(key: key);
#override
_PopUpmenusScreenState createState() => _PopUpmenusScreenState();
}
class _PopUpmenusScreenState extends State<PopUpmenusScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Examples"),
actions: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0,),
child: popMenus(
context: context,
options: [
{
"menu": "option 1" ?? '',
"menu_id": 1,
},
{
"menu": "option 2" ?? "",
"menu_id": 2,
},
{
"menu": "option 3" ?? "",
"menu_id": 3,
},
{
"menu": "option 4" ?? "",
"menu_id": 4,
},
],
),
)
],
),
);
}
}
Solution 2:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Examples"),
actions: [
IconButton(
icon: Icon(
Icons.more_horiz,
color: Colors.black,
size: 20.0,
),
onPressed: () {},
)
],
),
);
}
You can do that easily using DropdownButton2 which is customizable Flutter's core DropdownButton.
It has customButton parameter which will replace the normal Button with Image, Icon or any widget you want. You can customize everything and design what you need by using many options described with the package. Also, you can change the position of the dropdown menu by using the offset parameter.
Here's an example of using DropdownButton2 as a Popup Menu with Icon:
class CustomButtonTest extends StatefulWidget {
const CustomButtonTest({Key? key}) : super(key: key);
#override
State<CustomButtonTest> createState() => _CustomButtonTestState();
}
class _CustomButtonTestState extends State<CustomButtonTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
customButton: const Icon(
Icons.list,
size: 46,
color: Colors.red,
),
customItemsIndexes: const [3],
customItemsHeight: 8,
items: [
...MenuItems.firstItems.map(
(item) =>
DropdownMenuItem<MenuItem>(
value: item,
child: MenuItems.buildItem(item),
),
),
const DropdownMenuItem<Divider>(enabled: false, child: Divider()),
...MenuItems.secondItems.map(
(item) =>
DropdownMenuItem<MenuItem>(
value: item,
child: MenuItems.buildItem(item),
),
),
],
onChanged: (value) {
MenuItems.onChanged(context, value as MenuItem);
},
itemHeight: 48,
itemWidth: 160,
itemPadding: const EdgeInsets.only(left: 16, right: 16),
dropdownPadding: const EdgeInsets.symmetric(vertical: 6),
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.redAccent,
),
dropdownElevation: 8,
offset: const Offset(0, 8),
),
),
),
);
}
}
class MenuItem {
final String text;
final IconData icon;
const MenuItem({
required this.text,
required this.icon,
});
}
class MenuItems {
static const List<MenuItem> firstItems = [home, share, settings];
static const List<MenuItem> secondItems = [logout];
static const home = MenuItem(text: 'Home', icon: Icons.home);
static const share = MenuItem(text: 'Share', icon: Icons.share);
static const settings = MenuItem(text: 'Settings', icon: Icons.settings);
static const logout = MenuItem(text: 'Log Out', icon: Icons.logout);
static Widget buildItem(MenuItem item) {
return Row(
children: [
Icon(
item.icon,
color: Colors.white,
size: 22
),
const SizedBox(
width: 10,
),
Text(
item.text,
style: const TextStyle(
color: Colors.white,
),
),
],
);
}
static onChanged(BuildContext context, MenuItem item) {
switch (item) {
case MenuItems.home:
//Do something
break;
case MenuItems.settings:
//Do something
break;
case MenuItems.share:
//Do something
break;
case MenuItems.logout:
//Do something
break;
}
}
}
This is the output that I want. I am still new in flutter so can anyone let me know if there is already a widget for this kind of switch or how should I make one ??
Also, I want the data shown below this button to change if I choose the other button but I guess that's obvious.
Thanks in advance.
You can use the TabBar widget to achieve this. I added a full example demonstrating how you can create this using the TabBar widget:
CODE
class StackOver extends StatefulWidget {
#override
_StackOverState createState() => _StackOverState();
}
class _StackOverState extends State<StackOver>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Tab bar',
),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
// give the tab bar a height [can change hheight to preferred height]
Container(
height: 45,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(
25.0,
),
),
child: TabBar(
controller: _tabController,
// give the indicator a decoration (color and border radius)
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
25.0,
),
color: Colors.green,
),
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
tabs: [
// first tab [you can add an icon using the icon property]
Tab(
text: 'Place Bid',
),
// second tab [you can add an icon using the icon property]
Tab(
text: 'Buy Now',
),
],
),
),
// tab bar view here
Expanded(
child: TabBarView(
controller: _tabController,
children: [
// first tab bar view widget
Center(
child: Text(
'Place Bid',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
// second tab bar view widget
Center(
child: Text(
'Buy Now',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
),
);
}
}
OUTPUT
Try out this you have to change some colour and font:-
import 'package:flutter/material.dart';
typedef SwitchOnChange = Function(int);
class CustomSwitch extends StatefulWidget {
SwitchOnChange onChange;
CustomSwitch({this.onChange});
#override
State<StatefulWidget> createState() {
return CustomSwitchState();
}
}
class CustomSwitchState extends State<CustomSwitch>
with TickerProviderStateMixin {
AnimationController controller;
Animation animation;
GlobalKey key = GlobalKey();
#override
void initState() {
Future.delayed(Duration(milliseconds: 100)).then((v) {
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 300));
tabWidth = key.currentContext.size.width / 2;
// var width = (media.size.width - (2 * media.padding.left)) / 2;
animation = Tween<double>(begin: 0, end: tabWidth).animate(controller);
setState(() {});
controller.addListener(() {
setState(() {});
});
});
super.initState();
}
var selectedValue = 0;
double tabWidth = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
selectedValue == 0 ? this.controller.forward() : controller.reverse();
selectedValue = selectedValue == 0 ? 1 : 0;
},
child: Container(
key: key,
height: 44,
decoration: BoxDecoration(
color: Colors.grey, borderRadius: BorderRadius.circular(22)),
child: Stack(
children: <Widget>[
Row(
children: <Widget>[
Transform.translate(
offset: Offset(animation?.value ?? 0, 0),
child: Container(
height: 44,
width: tabWidth,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(color: Colors.grey, blurRadius: 3),
]),
),
),
],
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: tabWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.directions_walk),
SizedBox(width: 5),
Text("Place Bid")
],
),
),
Container(
width: tabWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.directions_walk),
SizedBox(width: 5),
Text("Buy now")
],
),
)
],
),
),
],
),
),
);
}
}
The following is my workaround, which I believe to be the best method.
import 'package:flutter/material.dart';
class SettingsScreen extends StatelessWidget {
const SettingsScreen({
super.key,
});
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Settings'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(AppBar().preferredSize.height),
child: Container(
height: 50,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
10,
),
color: Colors.grey[200],
),
child: TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
10,
),
color: Colors.pink,
),
tabs: const [
Tab(
text: 'Basic',
),
Tab(
text: 'Advanced',
)
],
),
),
),
),
),
body: const TabBarView(
children: [
Center(
child: Text(
'Basic Settings',
style: TextStyle(
fontSize: 30,
),
),
),
Center(
child: Text(
'Advanced Settings',
style: TextStyle(
fontSize: 30,
),
),
),
],
),
),
);
}
}
You can use also PageView widget.
const double borderRadius = 25.0;
class CustomSwitchState extends StatefulWidget {
#override
_CustomSwitchStateState createState() => _CustomSwitchStateState();
}
class _CustomSwitchStateState extends State<CustomSwitchState> with SingleTickerProviderStateMixin {
PageController _pageController;
int activePageIndex = 0;
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: _menuBar(context),
),
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
physics: const ClampingScrollPhysics(),
onPageChanged: (int i) {
FocusScope.of(context).requestFocus(FocusNode());
setState(() {
activePageIndex = i;
});
},
children: <Widget>[
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Center(child: Text("Place Bid"),),
),
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Center(child: Text("Buy Now"),),
),
],
),
),
],
),
),
),
));
}
Widget _menuBar(BuildContext context) {
return Container(
width: 300.0,
height: 50.0,
decoration: const BoxDecoration(
color: Color(0XFFE0E0E0),
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
onTap: _onPlaceBidButtonPress,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
decoration: (activePageIndex == 0) ? const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
) : null,
child: Text(
"Place Bid",
style: (activePageIndex == 0) ? TextStyle(color: Colors.white) : TextStyle(color: Colors.black),
),
),
),
),
Expanded(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
onTap: _onBuyNowButtonPress,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
decoration: (activePageIndex == 1) ? const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
) : null,
child: Text(
"Buy Now",
style: (activePageIndex == 1) ? TextStyle(color: Colors.white, fontWeight: FontWeight.bold) : TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
),
),
),
),
],
),
);
}
void _onPlaceBidButtonPress() {
_pageController.animateToPage(0,
duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
}
void _onBuyNowButtonPress() {
_pageController.animateToPage(1,
duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
}
}
OUTPUT
If you want tab layout like this you can use this
Output:
import 'package:flutter/material.dart';
import 'package:icons_helper/icons_helper.dart';
class DetailScreen extends StatefulWidget {
var body;
String title = "";
DetailScreen(this.body, this.title);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<DetailScreen> with TickerProviderStateMixin {
late int _startingTabCount;
List<Tab> _tabs = <Tab>[];
List<Widget> _generalWidgets = <Widget>[];
late TabController _tabController;
#override
void initState() {
_startingTabCount = widget.body["related_modules"].length;
_tabs = getTabs(_startingTabCount);
_tabController = getTabController();
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
bottom: TabBar(
isScrollable: true,
tabs: _tabs,
controller: _tabController,
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.grey,
Colors.blue,
],
stops: [0.3, 1.0],
),
),
),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(context);
},
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.skip_previous),
color: Colors.white,
onPressed: () {
goToPreviousPage();
},
),
Container(
margin: EdgeInsets.only(right: 15),
child: IconButton(
icon: Icon(Icons.skip_next),
color: Colors.white,
onPressed: () {
goToNextPage();
},
),
)
],
),
body: Column(
children: <Widget>[
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: _tabController,
children: getWidgets(),
),
),
],
),
);
}
TabController getTabController() {
return TabController(length: _tabs.length, vsync: this)
..addListener(_updatePage);
}
Tab getTab(int widgetNumber) {
return Tab(
icon: Column(
children: [
if (widget.body["related_modules"][widgetNumber]["icon"].toString() ==
"fa-comments-o") ...[
Icon(
Icons.comment_outlined,
),
] else if (widget.body["related_modules"][widgetNumber]["icon"]
.toString() ==
"fa-map-marker") ...[
Icon(
Icons.location_on_rounded,
),
] else if (widget.body["related_modules"][widgetNumber]["icon"]
.toString() ==
"fa-address-card") ...[
Icon(
Icons.contact_page_sharp,
),
] else ...[
Icon(
getIconUsingPrefix(
name: widget.body["related_modules"][widgetNumber]["icon"]
.toString()
.substring(3),
),
)
]
],
),
text: widget.body["related_modules"][widgetNumber]["label"].toString(),
);
}
Widget getWidget(int widgetNumber) {
return Center(
child: Text("Widget nr: $widgetNumber"),
);
}
List<Tab> getTabs(int count) {
_tabs.clear();
for (int i = 0; i < count; i++) {
_tabs.add(getTab(i));
}
return _tabs;
}
List<Widget> getWidgets() {
_generalWidgets.clear();
for (int i = 0; i < _tabs.length; i++) {
_generalWidgets.add(getWidget(i));
}
return _generalWidgets;
}
void _updatePage() {
setState(() {});
}
//Tab helpers
bool isFirstPage() {
return _tabController.index == 0;
}
bool isLastPage() {
return _tabController.index == _tabController.length - 1;
}
void goToPreviousPage() {
_tabController.animateTo(_tabController.index - 1);
}
void goToNextPage() {
isLastPage()
? showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("End reached"),
content: Text("This is the last page.")))
: _tabController.animateTo(_tabController.index + 1);
}
}