i have setup banner ads in flutter and those are overlapping the bottom navigation bar
I want to display ads below that bottom navigation bar,
is there any way that i can add a margin below the bottom navigation bar ?
i have implemented ads in home.dart (mainpage)
import 'package:provider/provider.dart';
import '../../ui/widgets/bottom_nav_bar.dart';
import '../../core/utils/theme.dart';
import 'search_page.dart';
import 'category.dart';
import 'main_page.dart';
import 'settings.dart';
import 'package:flutter/material.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'for_you.dart';
const String AD_MOB_APP_ID = 'ca-app-pub-3940256099942544~3347511713';
const String AD_MOB_TEST_DEVICE = 'DEC3010B2445165B43EB949F5D97D0F8 - run ad then check device logs for value';
const String AD_MOB_AD_ID = 'ca-app-pub-3940256099942544/6300978111';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
BannerAd _bannerAd;
static final MobileAdTargetingInfo targetingInfo = new MobileAdTargetingInfo(
testDevices: <String>[AD_MOB_TEST_DEVICE],
);
BannerAd createBannerAd() {
return new BannerAd(
adUnitId: AD_MOB_AD_ID,
size: AdSize.banner,
targetingInfo: targetingInfo,
);
}
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
int _selectedIndex = 0;
final PageController _pageController = PageController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
#override
Widget build(BuildContext context) {
final stateData = Provider.of<ThemeNotifier>(context);
final ThemeData state = stateData.getTheme();
FirebaseAdMob.instance.initialize(appId: AD_MOB_APP_ID);
_bannerAd = createBannerAd()..load()..show();
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: false,
backgroundColor: state.primaryColor,
elevation: 0,
title: Text(
'RevWalls',
style: state.textTheme.headline,
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: state.textTheme.body1.color,
),
onPressed: () => showSearch(
context: context, delegate: WallpaperSearch(themeData: state)),
)
],
),
body: Container(
color: state.primaryColor,
child: PageView(
controller: _pageController,
physics: BouncingScrollPhysics(),
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
children: <Widget>[
MainBody(),
Category(),
ForYou(),
SettingsPage(),
],
),
),
bottomNavigationBar: BottomNavyBar(
selectedIndex: _selectedIndex,
unselectedColor: state.textTheme.body1.color,
onItemSelected: (index) {
_pageController.jumpToPage(index);
},
selectedColor: state.accentColor,
backgroundColor: state.primaryColor,
showElevation: false,
items: [
BottomNavyBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavyBarItem(
icon: Icon(Icons.category),
title: Text('Subreddits'),
),
BottomNavyBarItem(
icon: Icon(Icons.phone_android),
title: Text('Exact Fit'),
),
BottomNavyBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
],
),
);
}
Widget oldBody(ThemeData state) {
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: state.primaryColor,
elevation: 4,
title: Text(
'reWalls',
style: state.textTheme.headline,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search, color: state.accentColor),
onPressed: () {
showSearch(
context: context,
delegate: WallpaperSearch(themeData: state));
},
)
],
floating: true,
pinned: _selectedIndex == 0 ? false : true,
snap: false,
centerTitle: false,
),
];
},
body: Container(
color: state.primaryColor,
child: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
children: <Widget>[
MainBody(),
Category(),
ForYou(),
SettingsPage(),
],
),
),
);
}
}
and here is the bottom navigation bar -
library bottom_navy_bar;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class BottomNavyBar extends StatelessWidget {
final int selectedIndex;
final double iconSize;
final Color backgroundColor, selectedColor, unselectedColor;
final bool showElevation;
final Duration animationDuration;
final List<BottomNavyBarItem> items;
final ValueChanged<int> onItemSelected;
BottomNavyBar(
{Key key,
this.selectedIndex = 0,
this.showElevation = true,
this.iconSize = 20,
this.backgroundColor,
this.selectedColor,
this.unselectedColor,
this.animationDuration = const Duration(milliseconds: 250),
#required this.items,
#required this.onItemSelected}) {
assert(items != null);
assert(items.length >= 2 && items.length <= 5);
assert(onItemSelected != null);
}
Widget _buildItem(BottomNavyBarItem item, bool isSelected) {
return AnimatedContainer(
width: isSelected ? 120 : 50,
height: double.maxFinite,
duration: animationDuration,
decoration: BoxDecoration(
color: isSelected ? selectedColor.withOpacity(0.2) : backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
),
alignment: Alignment.center,
child: ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconTheme(
data: IconThemeData(
size: iconSize,
color: isSelected ? selectedColor : unselectedColor),
child: item.icon,
),
),
isSelected
? DefaultTextStyle.merge(
style: TextStyle(
fontSize: 16,
color: selectedColor,
fontWeight: FontWeight.bold),
child: item.title,
)
: SizedBox.shrink()
],
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: backgroundColor,
child: Container(
width: double.infinity,
height: 55,
padding: EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: items.map((item) {
var index = items.indexOf(item);
return GestureDetector(
onTap: () {
onItemSelected(index);
},
child: _buildItem(item, selectedIndex == index),
);
}).toList(),
),
),
);
}
}
class BottomNavyBarItem {
final Icon icon;
final Text title;
BottomNavyBarItem({
#required this.icon,
#required this.title,
}) {
assert(icon != null);
assert(title != null);
}
}
Please help
adding builder like this will solve the problem
var paddingBottom = 60.0;
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
//
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'World General info',
//theme: ThemeData(primarySwatch: Colors.cyan,),
theme: ThemeData(
primaryColor: Colors.cyan,
accentColor: Colors.green,
textTheme: TextTheme(bodyText2: TextStyle(color: Colors.purple)),
),
home: MyHomePage(title: 'World General Info'),
builder: (BuildContext context, Widget child) {
return new Padding(
child: child,
padding: EdgeInsets.only(bottom: paddingBottom),
);
}
);
}
}
You can use the MediaQuery.of(context) for that.
Wrap the whole Code inside a Container of height: MediaQuery.of(context).size.height - 60 . (the height of ad)
Column(
children: [
Container(
height: MediaQuery.of(context).size.height - 60,
child: HomePage(),
),
BannerAd(),
]
);
Found answer myself
We can use this to set margin in a container with other things like height, width
This code will add margin to bottom of bottom nav bar, as per my need i want to show ads below navbar so this solves my problem
margin: const EdgeInsets.only(bottom: 50)
Add this to your scaffold
bottomNavigationBar: Container(
height: 50.0,
color: Colors.white,
),
Think Simplest bro ... wrap your column ( mainAxisSize : MainAxisSize.min )
Scaffold(
appBar: _appBar,
body: _body,
bottomNavigationBar: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Container(
color: Colors.amber,
height: 70,
child: Center(child: Text('Banner'))),
BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'),
BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add')
],
)
]))
Related
I am trying to understand how to return to a specific bottomtabindex. As of now I used a hacky way of ading an index to the HomePage as you can see. The issue is I cannot pass the Game object to HomePage too because it feels anti pattern. The Game Object should be passed to the Game Widget/screen
class HomePage extends StatefulWidget {
final int? index;
const HomePage({Key? key, this.index}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
_selectedIndex = widget.index?? 0;
}
int _selectedIndex = 0;
void _navigateBottomBar(int index) {
setState(() {
_selectedIndex = index;
});
}
List<Widget> screens = [
GameHistory(),
Game(),
Statistics(),
Help()
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: <Widget>[
Image.asset("assets/images/logo.png", width: 50, height:50),
const Text("App")
],
),
backgroundColor: Colors.deepPurple,
elevation: 10.0,
actions: [
IconButton(
icon: const Icon(Icons.add_circle_outline),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const CreateGame()),
);
},
)
],
),
body: screens[_selectedIndex],
bottomNavigationBar: Container(
color: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20),
child: GNav(
selectedIndex: _selectedIndex,
onTabChange: _navigateBottomBar,
backgroundColor: Colors.black,
color: Colors.white,
activeColor: Colors.white,
tabBackgroundColor: Colors.deepPurple,
gap: 8,
padding: const EdgeInsets.all(16),
tabs: const [
GButton(icon: Icons.home, text: 'Home'),
GButton(icon: Icons.play_arrow_outlined, text: 'Current Game',),
GButton(icon: Icons.leaderboard_outlined, text: 'Statistics'),
GButton(icon: Icons.help_outline_rounded, text: 'Rules'),
],),
),
);
}
}
//create game widget
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
elevation: 10.0,
title: const Text('Create Game'),
),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(12.0),
child: Text("Team names"),
),
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(children: [
Expanded(
child: TextField(
controller: player2,
decoration: const InputDecoration(hintText: "Playername"),
),
)
])
),
const Center(
child: Text("Versus",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(children: [
Expanded(
child: TextField(
controller: player4,
decoration: const InputDecoration(hintText: "Player name"),
),
)
])
),
const Text("Game Type"),
DropdownButton<String>(
value: gameType,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setState(() {
gameType = newValue!;
});
},
items: items
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Center(
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black),
),
onPressed: () {
// Navigate to game screen and pass information
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const HomePage(index: 1,)),
(route) =>false,
);
},
child: const Text('Start Game!'),
),
),
],
),
);
}
}
I wanted to send Data/images from one page to another. In the Homepage when I tap on the add Icon button image should be passed to the Cart page and if the icon is tapped again image is removed from the Cart page. But, the cart page should be accessed from bottom navigation bar.
but it is showing an error called 1 positional argument(s) expected, but 0 found.
Try adding the missing arguments.. when it calls the cart page.
HomePage.dart file
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: NavBar(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Dish> _dishes = List<Dish>();
List<Dish> _cartList = List<Dish>();
#override
void initState() {
super.initState();
_populateDishes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 16.0, top: 8.0),
child: GestureDetector(
child: Stack(
alignment: Alignment.topCenter,
children: <Widget>[
Icon(
Icons.shopping_cart,
size: 36.0,
),
if (_cartList.length > 0)
Padding(
padding: const EdgeInsets.only(left: 2.0),
child: CircleAvatar(
radius: 8.0,
backgroundColor: Colors.red,
foregroundColor: Colors.white,
child: Text(
_cartList.length.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.0,
),
),
),
),
],
),
onTap: () {
if (_cartList.isNotEmpty)
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Cart(_cartList),
),
);
},
),
)
],
),
body: _buildGridView(),
);
}
ListView _buildListView() {
return ListView.builder(
itemCount: _dishes.length,
itemBuilder: (context, index) {
var item = _dishes[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 2.0,
),
child: Card(
elevation: 4.0,
child: ListTile(
leading: Icon(
item.icon,
color: item.color,
),
title: Text(item.name),
trailing: GestureDetector(
child: (!_cartList.contains(item))
? Icon(
Icons.add_circle,
color: Colors.green,
)
: Icon(
Icons.remove_circle,
color: Colors.red,
),
onTap: () {
setState(() {
if (!_cartList.contains(item))
_cartList.add(item);
else
_cartList.remove(item);
});
},
),
),
),
);
},
);
}
GridView _buildGridView() {
return GridView.builder(
padding: const EdgeInsets.all(4.0),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: _dishes.length,
itemBuilder: (context, index) {
var item = _dishes[index];
return Card(
elevation: 4.0,
child: Stack(
fit: StackFit.loose,
alignment: Alignment.center,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
item.icon,
color: (_cartList.contains(item))
? Colors.grey
: item.color,
size: 100.0,
),
Text(
item.name,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.subhead,
)
],
),
Padding(
padding: const EdgeInsets.only(
right: 8.0,
bottom: 8.0,
),
child: Align(
alignment: Alignment.bottomRight,
child: GestureDetector(
child: (!_cartList.contains(item))
? Icon(
Icons.add_circle,
color: Colors.green,
)
: Icon(
Icons.remove_circle,
color: Colors.red,
),
onTap: () {
setState(() {
if (!_cartList.contains(item))
_cartList.add(item);
else
_cartList.remove(item);
});
},
),
),
),
],
));
});
}
void _populateDishes() {
var list = <Dish>[
Dish(
name: 'Chicken Zinger',
icon: Icons.fastfood,
color: Colors.amber,
),
Dish(
name: 'Chicken Zinger without chicken',
icon: Icons.print,
color: Colors.deepOrange,
),
Dish(
name: 'Rice',
icon: Icons.child_care,
color: Colors.brown,
),
Dish(
name: 'Beef burger without beef',
icon: Icons.whatshot,
color: Colors.green,
),
Dish(
name: 'Laptop without OS',
icon: Icons.laptop,
color: Colors.purple,
),
Dish(
name: 'Mac wihout macOS',
icon: Icons.laptop_mac,
color: Colors.blueGrey,
),
];
setState(() {
_dishes = list;
});
}
}
Cart.dart file
import 'package:flutter/material.dart';
import 'dish_object.dart';
class Cart extends StatefulWidget {
final List<Dish> _cart;
Cart(this._cart);
#override
_CartState createState() => _CartState(this._cart);
}
class _CartState extends State<Cart> {
_CartState(this._cart);
List<Dish> _cart;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
),
body: ListView.builder(
itemCount: _cart.length,
itemBuilder: (context, index) {
var item = _cart[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
child: Card(
elevation: 4.0,
child: ListTile(
leading: Icon(
item.icon,
color: item.color,
),
title: Text(item.name),
trailing: GestureDetector(
child: Icon(
Icons.remove_circle,
color: Colors.red,
),
onTap: () {
setState(() {
_cart.remove(item);
});
},
),
),
),
);
},
),
);
}
}
NavBar.dart file
import 'package:flutter/material.dart';
import 'package:sharewallpaper/cart.dart';
import 'package:sharewallpaper/main.dart';
class NavBar extends StatefulWidget {
#override
_NavBarState createState() => _NavBarState();
}
class _NavBarState extends State<NavBar> {
int _currentIndex = 0;
final List<Widget> _children = [
MyHomePage(),
Cart(), ** This line is throwing an error **
];
void onTappedBar(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTappedBar,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.bookmark), title: Text('BookMark')),
],
),
);
}
}
Yep, it will throw an error because the constructor of the Cart class is expecting one parameter to be passed in. You could use a named constructor instead like this:
class Cart extends StatefulWidget {
final List<Dish> _cart;
Cart(this._cart);
That way, you can call it like so:
Cart(cart: _cartList),
But if you actually need the cart list, I would recommend that you write a provider to keep track of the cart data across screens.
I have created a starter kit in flutter for a personal project.
Everything is working fine but I have an iisue where I'm unable to highlight the current selected item in the drawer.
I'm abit lost on where I chould be put the code that determins the current selected item.
Below is my code!
class _MdDrawerState extends State<MdDrawer>
with SingleTickerProviderStateMixin<MdDrawer> {
final _animationDuration = const Duration(milliseconds: 350);
AnimationController _animationController;
Stream<bool> isDrawerOpenStream;
StreamController<bool> isDrawerOpenStreamController;
StreamSink<bool> isDrawerOpenSink;
.....
#override
void dispose() {
_animationController.dispose();
isDrawerOpenStreamController.close();
isDrawerOpenSink.close();
super.dispose();
}
void onIconPressed() {
final animationStatus = _animationController.status;
final isAnimationCompleted = animationStatus == AnimationStatus.completed;
.....
}
#override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return StreamBuilder<bool>(
initialData: false,
stream: isDrawerOpenStream,
builder: (context, isLeftDrawerOpenedAsync) {
return AnimatedPositioned(
duration: _animationDuration,
top: 0,
bottom: 0,
left: isLeftDrawerOpenedAsync.data ? 0 : -screenWidth,
right: isLeftDrawerOpenedAsync.data ? 0 : screenWidth - 45,
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
color: Theme.of(context).backgroundColor,
child: ListView(
children: <Widget>[
Column(
children: <Widget>[
SizedBox(
height: 30,
),
ListTile(
title: Text('First - Last',
style: Theme.of(context).textTheme.headline),
subtitle: Text('something#gmail.com',
style: Theme.of(context).textTheme.subhead),
leading: CircleAvatar(
child: Icon(
Icons.perm_identity,
color: Theme.of(context).iconTheme.color,
),
radius: 40,
),
),
Divider(
height: 30,
),
MdNavItem(
icon: Icons.home,
title: 'Home',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.HomeClickedEvent);
},
),
MdNavItem(
icon: Icons.account_box,
title: 'Account',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.AccountClickedEvent);
},
),
MdNavItem(
icon: Icons.shopping_basket,
title: 'Orders',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.OrderClickedEvent);
},
),
MdNavItem(
icon: Icons.card_giftcard,
title: 'Wishlist',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.WishlistClickedEvent);
},
),
Divider(
height: 30,
),
MdNavItem(
icon: Icons.settings,
title: 'Settings',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.SettingsClickedEvent);
},
),
MdNavItem(
icon: Icons.exit_to_app,
title: 'Logout',
),
Divider(
height: 45,
),
],
),
],
),
),
),
......
],
),
);
},
);
}
}
And the MdNavItem class
class MdNavItem extends StatelessWidget {
final IconData icon;
final String title;
final Function onTap;
const MdNavItem({this.icon, this.title, this.onTap});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16),
child: Container(
color: Theme.of(context).backgroundColor,
child: Row(
children: <Widget>[
Icon(
icon,
size: 25,
color: Theme.of(context).iconTheme.color,
),
SizedBox(
width: 20,
),
Text(
title,
style: Theme.of(context).textTheme.headline,
),
],
),
),
),
);
}
}
Edit:
First Way to do:
Add this code to your Drawer:
class _MdDrawerState extends State<MdDrawer>
with SingleTickerProviderStateMixin<MdDrawer> {
final _animationDuration = const Duration(milliseconds: 350);
AnimationController _animationController;
Stream<bool> isDrawerOpenStream;
StreamController<bool> isDrawerOpenStreamController;
StreamSink<bool> isDrawerOpenSink;
final List<bool> isTaped = [true, false, false, false, false]; // the first is true because when the app
//launch the home needs to be in red(or the
//color you choose)
void changeHighlight(int index){
for(int indexTap = 0; indexTap < isTaped.length; indexTap++) {
if (indexTap == index) {
isTaped[index] = true; //used to change the value of the bool list
} else {
isTaped[indexTap] = false;
}
}
}
.....
#override
void dispose() {
_animationController.dispose();
isDrawerOpenStreamController.close();
isDrawerOpenSink.close();
super.dispose();
}
void onIconPressed() {
final animationStatus = _animationController.status;
final isAnimationCompleted = animationStatus == AnimationStatus.completed;
.....
}
#override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return StreamBuilder<bool>(
initialData: false,
stream: isDrawerOpenStream,
builder: (context, isLeftDrawerOpenedAsync) {
return AnimatedPositioned(
duration: _animationDuration,
top: 0,
bottom: 0,
left: isLeftDrawerOpenedAsync.data ? 0 : -screenWidth,
right: isLeftDrawerOpenedAsync.data ? 0 : screenWidth - 45,
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
color: Theme.of(context).backgroundColor,
child: ListView(
children: <Widget>[
Column(
children: <Widget>[
SizedBox(
height: 30,
),
ListTile(
title: Text('First - Last',
style: Theme.of(context).textTheme.headline),
subtitle: Text('something#gmail.com',
style: Theme.of(context).textTheme.subhead),
leading: CircleAvatar(
child: Icon(
Icons.perm_identity,
color: Theme.of(context).iconTheme.color,
),
radius: 40,
),
),
Divider(
height: 30,
),
MdNavItem(
wasTaped: isTaped[0],
icon: Icons.home,
title: 'Home',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.HomeClickedEvent);
changeHighlight(0);
},
),
MdNavItem(
wasTaped: isTaped[1],
icon: Icons.account_box,
title: 'Account',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.AccountClickedEvent);
changeHighlight(1);
},
),
MdNavItem(
wasTaped: isTaped[2],
icon: Icons.shopping_basket,
title: 'Orders',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.OrderClickedEvent);
changeHighlight(2);
},
),
MdNavItem(
wasTaped: isTaped[3],
icon: Icons.card_giftcard,
title: 'Wishlist',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.WishlistClickedEvent);
changeHighlight(3);
},
),
Divider(
height: 30,
),
MdNavItem(
wasTaped: isTaped[4],
icon: Icons.settings,
title: 'Settings',
onTap: () {
onIconPressed();
BlocProvider.of<MdNavBloc>(context)
.add(NavigationEvents.SettingsClickedEvent);
changeHighlight(4);
},
),
MdNavItem(
icon: Icons.exit_to_app,
title: 'Logout',
),
Divider(
height: 45,
),
],
),
],
),
),
),
......
],
),
);
},
);
}
}
And this to your MdNavItem:
class MdNavItem extends StatelessWidget {
final IconData icon;
final String title;
final Function onTap;
final bool wasTaped; //receiving the bool value (if was taped or not)
const MdNavItem({this.icon, this.title, this.onTap, this.wasTaped});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16),
child: Container(
color: Theme.of(context).backgroundColor,
child: Row(
children: <Widget>[
Icon(
icon,
size: 25,
color: wasTaped ? Colors.red : Theme.of(context).iconTheme.color, //the condition to change the color
),
SizedBox(
width: 20,
),
Text(
title,
style: wasTaped ? TextStyle(
color: Colors.red,
) : Theme.of(context).textTheme.headline,
),
],
),
),
),
);
}
}
Old answer, Second way:
First Screen, where PageView is placed:
class FirstScreen extends StatelessWidget {
final PageController pageController = PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kit App'),
),
drawer: CustomDrawer(
pageController: pageController,
),
body: PageView(
controller: pageController,
physics: NeverScrollableScrollPhysics(), //to prevent scroll
children: <Widget>[
HomeScreen(),
AccountScreen(), //your pages
OrdersScreen(),
WishListScreen(),
],
),
);
}
}
The CustomDrawer:
class CustomDrawer extends StatelessWidget {
CustomDrawer({#required this.pageController});
final PageController pageController;
#override
Widget build(BuildContext context) {
return Drawer(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20),
child: Column(
children: <Widget>[
DrawerItem(
onTap: (){
Navigator.pop(context); //to close the drawer
pageController.jumpToPage(0);
},
leading: Icons.home,
title: 'Home',
index: 0,
controller: pageController,
),
DrawerItem(
onTap: (){
Navigator.pop(context);
pageController.jumpToPage(1);
},
leading: Icons.account_box,
title: 'Account',
index: 1,
controller: pageController,
),
DrawerItem(
onTap: (){
Navigator.pop(context);
pageController.jumpToPage(2);
},
leading: Icons.shopping_cart,
title: 'Orders',
index: 2,
controller: pageController,
),
DrawerItem(
onTap: (){
Navigator.pop(context);
pageController.jumpToPage(3);
},
leading: Icons.card_travel,
title: 'Wishlist',
index: 3,
controller: pageController,
),
],
),
),
);
}
}
and the DrawerItem, where the condition to change the color of the items is placed:
class DrawerItem extends StatelessWidget {
DrawerItem({
#required this.onTap,
#required this.leading,
#required this.title,
#required this.index,
#required this.controller,
});
final VoidCallback onTap;
final IconData leading;
final String title;
final int index;
final PageController controller;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: ListTile(
leading: Icon(
leading,
color: controller.page.round() == index ? Colors.red : Colors.grey,
),
title: Text(
title,
style: TextStyle(
color: controller.page.round() == index ? Colors.red : Colors.grey,
),
),
),
);
}
}
Result:
Now you just need to implement this on your code.
Currently I'm using flutter package 'Reorderables' to show a reorderable listview which contains several images.These images can be deleted from listview through a button , everything works fine. But the listview rebuild everytime when I delete an image. I'm using a class called 'ReorderableListviewManager' with ChangeNotifier to update images and Provider.of<ReorderableListviewManager>(context) to get latest images . The problem now is that using Provider.of<ReorderableListviewManager>(context) makes build() called everytime I delete an image , so the listview rebuid. I koow I
can use consumer to only rebuild part of widget tree, but it seems like that there's no place to put consumer in children of this Listview. Is there a way to rebuild only image but not whole ReorderableListview ? Thanks very much!
Below is my code:
class NotePicturesEditScreen extends StatefulWidget {
final List<Page> notePictures;
final NotePicturesEditBloc bloc;
NotePicturesEditScreen({#required this.notePictures, #required this.bloc});
static Widget create(BuildContext context, List<Page> notePictures) {
return Provider<NotePicturesEditBloc>(
create: (context) => NotePicturesEditBloc(),
child: Consumer<NotePicturesEditBloc>(
builder: (context, bloc, _) =>
ChangeNotifierProvider<ReorderableListviewManager>(
create: (context) => ReorderableListviewManager(),
child: NotePicturesEditScreen(
bloc: bloc,
notePictures: notePictures,
),
)),
dispose: (context, bloc) => bloc.dispose(),
);
}
#override
_NotePicturesEditScreenState createState() => _NotePicturesEditScreenState();
}
class _NotePicturesEditScreenState extends State<NotePicturesEditScreen> {
PreloadPageController _pageController;
ScrollController _reorderableScrollController;
List<Page> notePicturesCopy;
int longPressIndex;
List<double> smallImagesWidth;
double scrollOffset = 0;
_reorderableScrollListener() {
scrollOffset = _reorderableScrollController.offset;
}
#override
void initState() {
Provider.of<ReorderableListviewManager>(context, listen: false)
.notePictures = widget.notePictures;
notePicturesCopy = widget.notePictures;
_reorderableScrollController = ScrollController();
_pageController = PreloadPageController();
_reorderableScrollController.addListener(_reorderableScrollListener);
Provider.of<ReorderableListviewManager>(context, listen: false)
.getSmallImagesWidth(notePicturesCopy, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
super.initState();
}
#override
void dispose() {
_pageController.dispose();
_reorderableScrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ReorderableListviewManager reorderableManager =
Provider.of<ReorderableListviewManager>(context, listen: false);
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
shape: Border(bottom: BorderSide(color: Colors.black12)),
iconTheme: IconThemeData(color: Colors.black87),
elevation: 0,
automaticallyImplyLeading: false,
titleSpacing: 0,
centerTitle: true,
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: IconButton(
padding: EdgeInsets.only(left: 20, right: 12),
onPressed: () => Navigator.of(context).pop(),
icon: Icon(Icons.close),
),
),
Text('編輯',
style: TextStyle(color: Colors.black87, fontSize: 18))
],
),
actions: <Widget>[
FlatButton(
onPressed: () {},
child: Text(
'下一步',
),
)
],
),
backgroundColor: Color(0xffeeeeee),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Spacer(),
StreamBuilder<List<Page>>(
initialData: widget.notePictures,
stream: widget.bloc.notePicturesStream,
builder: (context, snapshot) {
notePicturesCopy = snapshot.data;
return Container(
margin: EdgeInsets.symmetric(horizontal: 20),
height: MediaQuery.of(context).size.height * 0.65,
child: PreloadPageView.builder(
preloadPagesCount: snapshot.data.length,
controller: _pageController,
itemCount: snapshot.data.length,
onPageChanged: (index) {
reorderableManager.updateCurrentIndex(index);
reorderableManager.scrollToCenter(
smallImagesWidth,
index,
scrollOffset,
_reorderableScrollController,
context);
},
itemBuilder: (context, index) {
return Container(
child: Image.memory(
File.fromUri(
snapshot.data[index].polygon.isNotEmpty
? snapshot.data[index]
.documentPreviewImageFileUri
: snapshot.data[index]
.originalPreviewImageFileUri)
.readAsBytesSync(),
gaplessPlayback: true,
alignment: Alignment.center,
),
);
}),
);
},
),
Spacer(),
Container(
height: MediaQuery.of(context).size.height * 0.1,
margin: EdgeInsets.symmetric(horizontal: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ReorderableRow(
scrollController: _reorderableScrollController,
buildDraggableFeedback: (context, constraints, __) =>
Container(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Image.memory(File.fromUri(
notePicturesCopy[longPressIndex]
.polygon
.isNotEmpty
? notePicturesCopy[longPressIndex]
.documentPreviewImageFileUri
: notePicturesCopy[longPressIndex]
.originalPreviewImageFileUri)
.readAsBytesSync()),
),
onReorder: (oldIndex, newIndex) async {
List<Page> result = await widget.bloc.reorderPictures(
oldIndex,
newIndex,
reorderableManager.notePictures);
_pageController.jumpToPage(newIndex);
reorderableManager.updateNotePictures(result);
reorderableManager
.getSmallImagesWidth(result, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
},
footer: Container(
width: 32,
height: 32,
margin: EdgeInsets.only(left: 16),
child: SizedBox(
child: FloatingActionButton(
backgroundColor: Colors.white,
elevation: 1,
disabledElevation: 0,
highlightElevation: 1,
child: Icon(Icons.add, color: Colors.blueAccent),
onPressed: notePicturesCopy.length >= 20
? () {
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text('筆記上限為20頁 !'),
));
}
: () async {
List<Page> notePictures =
await widget.bloc.addPicture(
reorderableManager.notePictures);
List<double> imagesWidth =
await reorderableManager
.getSmallImagesWidth(
notePictures, context);
smallImagesWidth = imagesWidth;
reorderableManager.updateCurrentIndex(
notePictures.length - 1);
reorderableManager
.updateNotePictures(notePictures);
_pageController
.jumpToPage(notePictures.length - 1);
},
),
),
),
children: Provider.of<ReorderableListviewManager>(
context)
.notePictures
.asMap()
.map((index, page) {
return MapEntry(
index,
Consumer<ReorderableListviewManager>(
key: ValueKey('value$index'),
builder: (context, manager, _) =>
GestureDetector(
onTapDown: (_) {
longPressIndex = index;
},
onTap: () {
reorderableManager.scrollToCenter(
smallImagesWidth,
index,
scrollOffset,
_reorderableScrollController,
context);
_pageController.jumpToPage(index);
},
child: Container(
margin: EdgeInsets.only(
left: index == 0 ? 0 : 12),
decoration: BoxDecoration(
border: Border.all(
width: 1.5,
color: index ==
manager
.getCurrentIndex
? Colors.blueAccent
: Colors.transparent)),
child: index + 1 <=
manager.notePictures.length
? Image.memory(
File.fromUri(manager
.notePictures[
index]
.polygon
.isNotEmpty
? manager
.notePictures[
index]
.documentPreviewImageFileUri
: manager
.notePictures[
index]
.originalPreviewImageFileUri)
.readAsBytesSync(),
gaplessPlayback: true,
)
: null),
),
));
})
.values
.toList()),
)),
Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.black12))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
onPressed: () async => await widget.bloc
.cropNotePicture(reorderableManager.notePictures,
_pageController.page.round())
.then((notePictures) {
reorderableManager.updateNotePictures(notePictures);
reorderableManager
.getSmallImagesWidth(notePictures, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
}),
child: Column(
children: <Widget>[
Icon(
Icons.crop,
color: Colors.blueAccent,
),
Container(
margin: EdgeInsets.only(top: 1),
child: Text(
'裁切',
style: TextStyle(color: Colors.blueAccent),
),
)
],
),
),
FlatButton(
onPressed: () {
int deleteIndex = _pageController.page.round();
widget.bloc
.deletePicture(
reorderableManager.notePictures, deleteIndex)
.then((notePictures) {
if (deleteIndex == notePictures.length) {
reorderableManager
.updateCurrentIndex(notePictures.length - 1);
}
reorderableManager.updateNotePictures(notePictures);
reorderableManager
.getSmallImagesWidth(notePictures, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
if (reorderableManager.notePictures.length == 0) {
Navigator.pop(context);
}
});
},
child: Column(
children: <Widget>[
Icon(
Icons.delete_outline,
color: Colors.blueAccent,
),
Container(
margin: EdgeInsets.only(top: 1),
child: Text(
'刪除',
style: TextStyle(color: Colors.blueAccent),
),
),
],
),
)
],
),
)
],
)),
);
}
}
You can't prevent a rebuild on your ReorderableListView widget because it will be rebuild every time there's an update on the Provider. What you can do here is to keep track the current index of all visible ListView items. When new data should be displayed coming from the Provider, you can retain the current indices of previous ListView items, and add the newly added items at the end of the list, or wherever you like.
I want to create a horizontal stepper, which is easy I know, but this time, the count of steps should large.
Just to give an example, this is what I am doing for the vertical,
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget{
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
child: new ListView(
children: <Widget>[
new Text("Helllo "),
new Text( " Welcome"),
new Text (" Yaaa0"),
new SimpleWidget(),
],
), ),
);
}
}
class SimpleWidget extends StatefulWidget {
#override
SimpleWidgetState createState() => new SimpleWidgetState();
}
class SimpleWidgetState extends State<SimpleWidget> {
int stepCounter = 0;
List<Step> steps = [];
#override
void initState() {
prepareState();
super.initState();
}
void prepareState(){
for (var i= 0; i<100; i++){
var stepVal = new Step(
title:new Text("Step $i"),
content: new Text("This is the child of $i step"),
isActive: true,
);
steps.add(stepVal);
}
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Stepper(
type: StepperType.vertical,
physics : ClampingScrollPhysics(),
currentStep: this.stepCounter,
steps: steps,
onStepTapped: (step) {
setState(() {
stepCounter = step;
});
},
onStepCancel: () {
setState(() {
stepCounter > 0 ? stepCounter -= 1 : stepCounter = 0;
});
},
onStepContinue: () {
setState(() {
stepCounter < steps.length - 1 ? stepCounter += 1 : stepCounter = 0;
});
},
),
);
}
}
As soon as I try to recreate this in the horizontal mode, it shows nothing. I have tried to make the listView horizontal, I have tried to make the stepper horizontal, both individually and also together. None works. You can try that in the dartpad.
My question :
1. How to make a Stepper in horizontal that is scrollable in the horizontal mode.
2. The content of the Stepper is scrollable , I can see that. Can it be switched off?
use this class
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hexcolor/hexcolor.dart';
class StepProgressView extends StatelessWidget {
final double _width;
final List<String> _titles;
final int _curStep;
final Color _activeColor;
final Color _inactiveColor = HexColor("#E6EEF3");
final double lineWidth = 3.0;
StepProgressView(
{Key key,
#required int curStep,
List<String> titles,
#required double width,
#required Color color})
: _titles = titles,
_curStep = curStep,
_width = width,
_activeColor = color,
assert(width > 0),
super(key: key);
Widget build(BuildContext context) {
return Container(
width: this._width,
child: Column(
children: <Widget>[
Row(
children: _iconViews(),
),
SizedBox(
height: 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _titleViews(),
),
],
));
}
List<Widget> _iconViews() {
var list = <Widget>[];
_titles.asMap().forEach((i, icon) {
var circleColor = (i == 0 || _curStep > i + 1) ? _activeColor : _inactiveColor;
var lineColor = _curStep > i + 1 ? _activeColor : _inactiveColor;
var iconColor = (i == 0 || _curStep > i + 1) ? _activeColor : _inactiveColor;
list.add(
Container(
width: 20.0,
height: 20.0,
padding: EdgeInsets.all(0),
decoration: new BoxDecoration(
/* color: circleColor,*/
borderRadius: new BorderRadius.all(new Radius.circular(22.0)),
border: new Border.all(
color: circleColor,
width: 2.0,
),
),
child: Icon(
Icons.circle,
color: iconColor,
size: 12.0,
),
),
);
//line between icons
if (i != _titles.length - 1) {
list.add(Expanded(
child: Container(
height: lineWidth,
color: lineColor,
)));
}
});
return list;
}
List<Widget> _titleViews() {
var list = <Widget>[];
_titles.asMap().forEach((i, text) {
list.add(Text(text, style: TextStyle(color: HexColor("#000000"))));
});
return list;
}
}
declare list and int variable inside class you want to use
final List<String> titles = [TextConstant.CART, TextConstant.ADDRESS, TextConstant.PAYMENT];
int _curStep = 1;
finally use above class
StepProgressView(width: MediaQuery.of(context).size.width,
curStep: _curStep,
color: Color(0xff50AC02),
titles: titles),
try this example, e.g: conf pubspec file: fa_stepper: ^0.0.2, then flutter packages get , after that: using FAStepper constructor, define something like this:
Widget w1(BuildContext context) {
return Scaffold(
// Body
body: Container(
child: FAStepper(
// physics: ClampingScrollPhysics(),
// Using a variable here for handling the currentStep
currentStep: this.currentStep,
// List the steps you would like to have
titleHeight: 120,
steps: mySteps,
// Define the type of Stepper style
// StepperType.horizontal : Horizontal Style
// StepperType.vertical : Vertical Style
type: FAStepperType.horizontal,
titleIconArrange: FAStepperTitleIconArrange.column,
stepNumberColor: Colors.pinkAccent,
// Know the step that is tapped
onStepTapped: (step) {
// On hitting step itself, change the state and jump to that step
setState(() {
// update the variable handling the current step value
// jump to the tapped step
currentStep = step;
});
// Log function call
print("onStepTapped : " + step.toString());
},
onStepCancel: () {
// On hitting cancel button, change the state
setState(() {
// update the variable handling the current step value
// going back one step i.e subtracting 1, until its 0
if (currentStep > 0) {
currentStep = currentStep - 1;
} else {
currentStep = 0;
}
});
// Log function call
print("onStepCancel : " + currentStep.toString());
},
// On hitting continue button, change the state
onStepContinue: () {
setState(() {
// update the variable handling the current step value
// going back one step i.e adding 1, until its the length of the step
if (currentStep < mySteps.length - 1) {
currentStep = currentStep + 1;
} else {
currentStep = 0;
}
});
// Log function call
print("onStepContinue : " + currentStep.toString());
},
)),
);
}
You can create Horizontal Stepper in Flutter without any external package also like by following
This will work fine and use StatefulWidget to put this code inside it (StatefulWidget).
int _currentStep = 0;
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Expanded(
child: Stepper(
type: StepperType.horizontal,
physics: ScrollPhysics(),
currentStep: _currentStep,
onStepTapped: (step) => tapped(step),
onStepContinue: continued,
onStepCancel: cancel,
steps: <Step>[
Step(
title: new Text(''),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Email Address'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
),
],
),
isActive: _currentStep >= 0,
state: _currentStep >= 0 ?
StepState.complete : StepState.disabled,
),
Step(
title: new Text(''),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Home Address'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Postcode'),
),
],
),
isActive: _currentStep >= 0,
state: _currentStep >= 1 ?
StepState.complete : StepState.disabled,
),
Step(
title: new Text(''),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Mobile Number'),
),
],
),
isActive:_currentStep >= 0,
state: _currentStep >= 2 ?
StepState.complete : StepState.disabled,
),
Step(
title: new Text(''),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Mobile Number'),
),
],
),
isActive:_currentStep >= 0,
state: _currentStep >= 3 ?
StepState.complete : StepState.disabled,
),
],
),
),
],
),
);
}
tapped(int step){
setState(() => _currentStep = step);
}
continued(){
_currentStep < 3 ?
setState(() => _currentStep += 1): null;
}
cancel(){
_currentStep > 0 ?
setState(() => _currentStep -= 1) : null;
}
Wrap the stepper with a ConstrainedBox and set its height to a constant and make the StepperType as horizontal. You can check it in dartpad .
return ConstrainedBox(
constraints: BoxConstraints.tightFor(height: 500.0),
child: Stepper(
type: StepperType.horizontal,
),
);
There is an issue about this on github https://github.com/flutter/flutter/issues/40601
BUT
This is what i m using right now
output image
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme:ThemeData(
primarySwatch:Colors.amber
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class CustomStep {
final String title;
final Widget page;
CustomStep(
{#required this.title, #required this.page});
}
class MyWidget extends StatefulWidget {
const MyWidget({ Key key }) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
ScrollController _scrollController = new ScrollController();
static const double STEP_WIDTH = 90;
PageController pageController = PageController();
List<CustomStep> stepsList;
int currentPage=0;
#override
void initState() {
super.initState();
stepsList = [
CustomStep(
title: 'ddddd',
page: Placeholder(
color: Colors.pink,
),
),
CustomStep(
title: 'zzzzzzzz',
page: Placeholder(
color: Colors.deepPurple,
),
),
];
}
SizedBox buildStepDivider(int index) {
return SizedBox(
height: 90,
child: Container(
alignment: Alignment.topCenter,
child: Transform.translate(
offset: Offset(0, 16),
child: Container(
color: index < currentPage
? Theme.of(context).primaryColor
: Colors.grey,
width: 30,
height: 3,
padding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
);
}
buildStep(int index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: SizedBox(
height: 90,
width: STEP_WIDTH,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: index <= currentPage
? Theme.of(context).primaryColor
: Colors.grey[300],
),
padding: EdgeInsets.all(10),
child: Text((index + 1).toString()),
),
Expanded(
child: Text(
stepsList[index].title,
textAlign: TextAlign.center,
))
],
),
),
);
}
_buildStepper(int currentStep) {
Future.delayed(
Duration(milliseconds: 100),
() => _scrollController.animateTo((STEP_WIDTH * currentStep).toDouble(),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut));
return Center(
child: SizedBox(
height: 110,
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: stepsList.length,
itemBuilder: (ctx, index) => index < stepsList.length - 1
? Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
buildStep(index),
buildStepDivider(index)
],
)
:Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
buildStep(index)]) ),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('hello'), centerTitle: true),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_buildStepper(currentPage),
Expanded(
child: PageView.builder(
controller: pageController,
physics: NeverScrollableScrollPhysics(),
onPageChanged: (index) {
setState(() {
currentPage = index;
});
},
itemCount: stepsList.length,
itemBuilder: (ctx, index) =>
stepsList[index].page,
)),
],
),
);
}
}
I am sure you have got the answer, but maybe this is for someone who is looking for a package instead of creating a custom one. Here is something that I found good, please do check out and see if it fits in your use-case.
https://pub.dev/packages/im_stepper
A very easy step to create a number stepper is
Container(
margin: const EdgeInsets.only(top: 4, right: 6),
padding: const EdgeInsets.all(3.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.red, width: 2),
borderRadius: BorderRadius.circular(2),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
child: Icon(Icons.remove, color: Colors.red),
onTap: _dicrement,
),
Container(
margin: EdgeInsets.only(right: 8, left: 8),
child: Text(
_currentCount.toString(),
style: TextStyle(fontWeight: FontWeight.bold),
),
),
InkWell(
child: Icon(Icons.add, color: Colors.red),
onTap: _increment,
),
],
),
),