The material design guidelines used to have a section on steppers: https://material.io/archive/guidelines/components/steppers.html#steppers-types-of-steps . This included various stepper types, including "Mobile step progress bar":
Flutter has a Stepper class, but very scantily documented. How would I implement the kind of stepper seen above?
There's a request for documentation on Github that touches on this topic, but so far, there aren't any clear guides on how to implement this.
I don't think Flutter's stepper class is the same stepper you're talking about. It's meant for a completely different purpose.
As for your "stepper", it's actually quite simple to make one yourself. I've done it two ways - one with a LinearProgressIndicator and the other with a simple gradient, but you could also do it pretty easily with a custompaint.
I've included it within a PageView example as that seems to be what you're doing with it. Some of the PageView code is cribbed from the flutter gallery pagination example and actually that page would probably be worth looking at as well.
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
PageController _pageController = new PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PageView(
children: <Widget>[
Center(child: Text("Page 1")),
Center(child: Text("Page 2")),
Center(child: Text("Page 3")),
Center(child: Text("Page 4")),
],
controller: _pageController,
physics: AlwaysScrollableScrollPhysics(),
),
bottomNavigationBar: Column(
children: <Widget>[
Container(
height: 10,
margin: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
child: GradientPageIndicator(
pageController: _pageController,
pageCount: 4,
primaryColor: Colors.blue,
secondaryColor: Colors.blue.withOpacity(0.2),
),
),
Container(
height: 10,
margin: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
child: ProgressPageIndicator(
pageController: _pageController,
pageCount: 4,
primaryColor: Colors.blue,
secondaryColor: Colors.blue.withOpacity(0.2),
),
),
],
mainAxisSize: MainAxisSize.min,
),
),
);
}
}
class ProgressPageIndicator extends AnimatedWidget {
final PageController pageController;
final int pageCount;
final Color primaryColor;
final Color secondaryColor;
final num height;
ProgressPageIndicator({
#required this.pageController,
#required this.pageCount,
#required this.primaryColor,
#required this.secondaryColor,
this.height = 2.0,
}) : super(listenable: pageController);
#override
Widget build(BuildContext context) {
return Container(
height: height,
child: LinearProgressIndicator(
backgroundColor: secondaryColor,
valueColor: Tween(begin: primaryColor, end: primaryColor).animate(kAlwaysCompleteAnimation),
value: (pageController.page ?? pageController.initialPage) / (pageCount - 1),
),
);
}
}
class GradientPageIndicator extends AnimatedWidget {
final PageController pageController;
final int pageCount;
final Color primaryColor;
final Color secondaryColor;
final num height;
GradientPageIndicator({
#required this.pageController,
#required this.pageCount,
#required this.primaryColor,
#required this.secondaryColor,
this.height = 2.0,
}) : super(listenable: pageController);
#override
Widget build(BuildContext context) {
double pagePosition = (pageController.page ?? pageController.initialPage) / (pageCount - 1);
double alignPosition = pagePosition * 2 - 1;
print("PagePosition: $pagePosition, alignPosition: $alignPosition");
return Container(
height: height,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [primaryColor, secondaryColor],
begin: Alignment(alignPosition - 0.0001, 0),
end: Alignment(alignPosition + 0.0001, 0),
),
),
);
}
}
Related
Here is my code :
import 'package:all_in_one/cooking/pages/recipe/header/search_bar_header.dart';
import 'package:all_in_one/cooking/pages/recipe/header/welcome_header.dart';
import 'package:flutter/material.dart';
class MyRecipePage extends StatefulWidget {
final String title;
MyRecipePage({Key? key, required this.title}) : super(key: key);
#override
_MyRecipePageState createState() => _MyRecipePageState();
}
class _MyRecipePageState extends State<MyRecipePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
WelcomeHeader(),
SearchBarHeader(),
SliverGrid.count(),
],
) ,
);
}
}
class WelcomeHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return
SliverPersistentHeader(
floating: true,
delegate: SliverAppBarDelegate(
minHeight: 0,
maxHeight: 100,
child: Container(
color: Colors.white,
child: _MyWelcomingHeader(),
),
),
);
}
}
class _MyWelcomingHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Flexible(
child: CircleAvatar(
radius: 57,
backgroundColor: Colors.grey.shade50,
child: Image.asset("assets/emoji-food.jpg"),
),
),
Flexible(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Enjoy the recipes',
style: TextStyle(
color: Colors.black,
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
}
class SearchBarHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SliverPersistentHeader(
pinned: true,
delegate: SliverAppBarDelegate(
minHeight: 50,
maxHeight: 50,
child: Container(
color: Colors.white,
child: _MySearchBar(),
),
),
);
}
}
class _MySearchBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(width: 10),
const Icon(Icons.search, color: Colors.grey, size: 30),
const SizedBox(width: 5),
Text("Search product",
style: TextStyle(
color: Colors.grey.shade500, fontSize: 12, fontWeight: FontWeight.w200))
],
);
}
}
The code of the silver bar Delegate is from this stackoverflow post
import 'package:flutter/material.dart';
import 'dart:math' as math;
class SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final double minHeight;
final double maxHeight;
final Widget child;
SliverAppBarDelegate({
required this.minHeight,
required this.maxHeight,
required this.child,
});
#override
double get minExtent => minHeight;
#override
double get maxExtent => math.max(maxHeight, minHeight);
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
#override
bool shouldRebuild(SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
My goal is to have 2 SliverPersistentHeader, one pinned and one floating.
The one floating (the 1st one) should resize the text and the image while scrolling..
In the following screenshot, we can see that the 2nd SliverPersistentHeader is overlapping the 1st one.
How can I do to make the Text resize itself. I try to use Flexible like I did for the CircleAvatar but I can't succeed :/
Thanks
I found a solution using the opacity, so my WelcomeHeader becomes :
import 'package:flutter/material.dart';
import 'dart:math' as math;
class WelcomeHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SliverAppBar(
backgroundColor: Colors.white,
pinned: false,
floating: false,
snap: false,
expandedHeight: 120,
flexibleSpace: _MyWelcomingHeader()
);
}
}
class _MyWelcomingHeader extends StatelessWidget {
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, c) {
final settings = context
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
final deltaExtent = settings!.maxExtent - settings.minExtent;
final t =
(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
.clamp(0.0, 1.0);
final fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
const fadeEnd = 1.0;
final opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Opacity(
opacity: opacity,
child: Column(
children: [
Flexible(
child: CircleAvatar(
radius: 57,
backgroundColor: Colors.grey.shade50,
child: Image.asset("assets/emoji-food.jpg"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Enjoy the recipes !',
style: TextStyle(
color: Colors.black,
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
});
}
}
I have to implement a horizontal scroll list in flutter.I could do that and have included the code below(The code is still to be modified but the base of the code is good enough to put in the pictures and other such details)
But the problem is the minus bar below the horizontal scroll.I don't know what feature in flutter allows to do that.I search many things but other than radio boxes,check boxes, switches,etc I am not able to find any details of it.Please have a look at the screenshot of the app ,I have indicated the minus bar control in red.Home screen,the minus bar indicated in red
The code I have written:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black ,
body: Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
height: 500,
child: ListView(
// This next line does the trick.
scrollDirection: Axis.horizontal,
shrinkWrap: true,
children: <Widget>[
Container(
width:400 ,
color: Colors.red,
),
Container(
width: 400.0,
color: Colors.blue,
),
Container(
width: 400.0,
color: Colors.green,
),
],
),
)
);
}
}
What you want to look for is not ListView but PageView here is a small code sample to try in DartPad and see how you could make your layout.
Basically I am using a PageController to change the current page by taping on certain widgets.
Code
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(body: MyWidget()),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final _items = [Colors.red, Colors.blue, Colors.yellow];
final _pageController = PageController();
int _currentPageNotifier = 0;
final double _indicatorWidth = 30;
Widget _buildPageView() {
return PageView.builder(
controller: _pageController,
itemCount: _items.length,
itemBuilder: (context, index) => Center(
child: FlutterLogo(
colors: _items[index],
size: 50,
),
),
onPageChanged: (int index) =>
setState(() => _currentPageNotifier = index),
);
}
Widget _buildIndicator() {
List<Widget> itemWidgets = [];
for (int index = 0; index < _items.length; index++) {
itemWidgets.add(GestureDetector(
onTap: () => _pageController.animateToPage(
index,
duration: Duration(milliseconds: 300),
curve: Curves.ease,
),
child: Container(
decoration: BoxDecoration(
color: _currentPageNotifier == index
? Colors.green
: Colors.grey,
borderRadius: BorderRadius.circular(9),
),
margin: EdgeInsets.only(right: 10),
width: _indicatorWidth,
height: 8,
),
));
}
return Positioned(
bottom: MediaQuery.of(context).size.height / 2 - 50,
left: MediaQuery.of(context).size.width / 2 -
_items.length * _indicatorWidth +
_items.length * 10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: itemWidgets,
),
);
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
_buildPageView(),
_buildIndicator(),
],
);
}
}
I am creating a story app where two users telling the story like this in the below images. so here I want to create a dialog box like the below image. but I don't know how to create
You should be implement below way
class IntroPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _IntroPageState();
}
class _IntroPageState extends State<IntroPage>
with SingleTickerProviderStateMixin {
AnimationController animationController;
bool _menuShown = false;
#override
void initState() {
animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
super.initState();
}
#override
Widget build(BuildContext context) {
Animation opacityAnimation =
Tween(begin: 0.0, end: 1.0).animate(animationController);
if (_menuShown)
animationController.forward();
else
animationController.reverse();
return Scaffold(
backgroundColor: Colors.amberAccent,
body: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
right: 0,
top:90,
child: InkWell(
onTap: () {
setState(() {
_menuShown = !_menuShown;
});
},
child: Image.asset(
'assets/images/girls.png',
height: 250,
),
),
),
Positioned(
child: FadeTransition(
opacity: opacityAnimation,
child: _DialogUI(),
),
right: 40.0,
top: 300.0,
),
],
),
);
}
}
class _DialogUI extends StatelessWidget {
_DialogUI();
final double padding = 8.0;
#override
Widget build(BuildContext context) {
return Center(
child: Material(
clipBehavior: Clip.antiAlias,
shape: _DialogShapeBorder(
borderRadius: BorderRadius.all(Radius.circular(padding)),
padding: padding),
elevation: 4.0,
child: Container(
margin: const EdgeInsets.all(10),
padding: EdgeInsets.all(padding).copyWith(bottom: padding * 2),
child: Center(
child: Text(
'Filler text is text that shares \nsome characteristics of a real written text, \n but is random or otherwise generated.\n It may be used to display a sample of fonts,\n generate text for testing, or to spoof an e-mail spam filter.'),
),
)),
);
}
}
class _DialogShapeBorder extends RoundedRectangleBorder {
_DialogShapeBorder({
#required this.padding,
side = BorderSide.none,
borderRadius = BorderRadius.zero,
}) : super(side: side, borderRadius: borderRadius);
final double padding;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
return Path()
..moveTo(rect.width - 18.0, rect.top)
..lineTo(rect.width - 20.0, rect.top - 36.0)
..lineTo(rect.width - 52.0, rect.top)
..addRRect(borderRadius.resolve(textDirection).toRRect(Rect.fromLTWH(
rect.left, rect.top, rect.width, rect.height - padding)));
}
}
Output
I want to create a website using flutter web but I'm unable to navigate to sections in the same page. Here's an example of what I want to achieve using flutter.
P.S. Navigator is not working:
I created an example with PageView
class MyHomePage extends StatelessWidget {
var list = ["Home","Services", "Work", "About"];
var colors = [Colors.orange, Colors.blue, Colors.red, Colors.green];
PageController controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 50,
height: 50,
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10)
),
),
Spacer(),
Row(
children: List.generate(3, (index){
return GestureDetector(
onTap: (){
_scrollToIndex(index);
},
child: Container(
margin: EdgeInsets.all(8),
child: Text(
list[index+1]
),
),
);
}),
)
],
),
Expanded(
child : PageView(
scrollDirection: Axis.vertical,
pageSnapping: false,
controller: controller,
children: List.generate(list.length, (index){
return Container(
width: MediaQuery.of(context).size.width,
height: double.maxFinite,
color: colors[index],
child: Center(
child: Text(
list[index],
style: TextStyle(
color: Colors.white,
fontSize: 50
),
),
),
);
})
),
),
],
)
),
);
}
void _scrollToIndex(int index) {
controller.animateToPage(index + 1, duration: Duration(seconds: 2), curve: Curves.fastLinearToSlowEaseIn);
}
}
The output:
ScrollController is the thing you are looking for.
Add a new one to your ScrolView and you can set where you want it to scroll to.
Josteve mentioned a way of doing it. But I'd like to show the other way which provides more features as one would expect in the gif example you have put.
You can see the demo here: https://mohith7548.github.io/portfolio/
My project has 3 sections called About, Blog & Projects. It also has another top section called Home. So the order of screens is Home, About, Blog & Projects. Each section takes full-screen height & width. So the starting offset for these pages are [0 * screenHeight, 1 * screenHeight, 2 * screenHeight, 3 * screenHeight] respectively. screenHeight can be accessed by MediaQuery.of(context).size.height inside build method.
class Portfolio extends StatefulWidget {
#override
_PortfolioState createState() => _PortfolioState();
}
class _PortfolioState extends State<Portfolio> {
ScrollController _scrollController;
String _curNavItem;
static double offsetHome = 0;
static double offsetAbout = SizeConfig.screenHeight;
static double offsetBlog = 2 * SizeConfig.screenHeight;
static double offsetProjects = 3 * SizeConfig.screenHeight;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
}
#override
void dispose() {
super.dispose();
_scrollController.dispose();
}
void scrollTo(String title) {
double offset = 0;
switch (title) {
case Constants.HOME:
offset = offsetHome;
break;
case Constants.ABOUT:
offset = offsetAbout;
break;
case Constants.BLOG:
offset = offsetBlog;
break;
case Constants.PROJECTS:
offset = offsetProjects;
break;
}
setState(() {
_curNavItem = title;
});
// animate to the pag
_scrollController.animateTo(
offset,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutQuart,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: PageScrollPhysics(), // use NeverScrollableScrollPhysics() to block user scrolling
controller: _scrollController,
slivers: <Widget>[
// This is just SliverAppBar wrapped in InterheritedWidget called NavState
// You can use just SliverAppBar
NavState(
curNavItem: _curNavItem,
scrollTo: scrollTo,
child: AppBanner(key: _appBannerKey), // SliverAppBar in another file
),
SliverList(
delegate: SliverChildListDelegate([
About(),
Blog(),
Projects(),
]),
)
],
),
);
}
}
You can do this in different ways:
TabBarView https://stackoverflow.com/a/60624536/10976088
PageView https://stackoverflow.com/a/60778791/10976088
NavigationRail https://api.flutter.dev/flutter/material/NavigationRail-class.html
My method: Using a state management way to keep name or index of content pages and change visible page. I do it with the Riverpod package here:
Suppose you want to have a fixed SidebarView and HeaderView in all pages and also you have a ContentPage that will be changed.
So you can have a RootPage including these 3 sections and change ContentPage by the riverpod, so that only ContentPage will be changed.
class RootPage extends StatelessWidget {
const RootPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: SidebarView(),
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Responsive.isDesktop(context))
const Expanded(
flex: 1,
child: SidebarView(),
),
Expanded(
flex: 5,
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HeaderView(),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Consumer(
builder: (context, ref, _) {
var watch = ref.watch(pageVisibleStateProvider);
return contentPageSelection(watch.state);
},
),
),
),
],
),
),
),
],
),
);
}
}
simply change content page:
Widget contentPageSelection(String pageName){
switch(pageName){
case "page1":
return Page1();
case "page2":
return Page2();
case "page3":
return Page3();
default:
return DefaultPage();
}
}
where:
final pageVisibleStateProvider = StateProvider<String>((_) => "defaultPage");
and:
class SidebarView extends StatelessWidget {
const SidebarView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("sidebar content"),
);
}
}
class HeaderView extends StatelessWidget {
const HeaderView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("HeaderView content"),
);
}
}
Now you can change content page. for example you want to show Page2:
ElevatedButton(
onPressed: (){
ref.read(pageVisibleStateProvider.notifier).state = "page2";
},
child: Text("go to page 2"),
)
where page2 and other content pages only includes content not sidebar or header:
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text("page2 content");
}
}
I want to create slider animation with Images and also want to allow user to use swipe gesture to move back and forth. Another requirement is Page indicator. For this purpose, I used
page_indicator: ^0.1.3
Currently I am able to slide between images using swipe gesture with page indicator and now I want to animate slides repeatedly with x amount of duration.
My code is below.
final PageController controller = new PageController();
#override
Widget build(BuildContext context) {
List<Widget> list = new List<Widget>();
list.add(new SliderBox(image: 'assets/shirt.png'));
list.add(new SliderBox(image: 'assets/laptops.png'));
list.add(new SliderBox(image: 'assets/bags.png'));
PageIndicatorContainer container = new PageIndicatorContainer(
pageView: new PageView(
children: list,
controller: controller,
),
length: 3,
padding: EdgeInsets.fromLTRB(10, 40, 10, 10),
indicatorSpace: 10,
indicatorColor: Colors.grey[350],
indicatorSelectorColor: Colors.grey,
);
return Stack(children: <Widget>[
Container(color: Colors.grey[100], height: double.infinity),
Container(
color: Colors.white,
child: container,
margin: EdgeInsets.only(bottom: 50)),Text('$moveToPage')
]);
class SliderBox extends StatelessWidget {
final image;
const SliderBox({Key key, this.image}) : super(key: key);
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
padding: EdgeInsets.all(10),
child: Image.asset(
image,
height: 300,
fit: BoxFit.fill,
));
}
}
I've modified your widget a little in order to provide you a complete example. You can do this in multiple ways, with AnimationController by itself or even combined with a custom Animation or, you can go the fastest way for what it seems that you want to achieve: using a recursive method that waits a x duration (time stand on a single page) and then animates with some new duration to the new page. For that you can for example:
Make your List available within the state itself in order to retrieve its length.
Create the recursive method that will handle the animation itself.
Make sure that you call it after the first frame is rendered on the screen to prevent the PageController of being accessed before having a PageView rendered on the screen, which you probably don't want. For that you take advantage of the WidgetsBinding.instance.addPostFrameCallback.
class Carousel extends StatefulWidget {
_CarouselState createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> with SingleTickerProviderStateMixin {
final PageController _controller = PageController();
List<Widget> _list = [
SliderBox(
child: FlutterLogo(
colors: Colors.red,
)),
SliderBox(
child: FlutterLogo(
colors: Colors.green,
)),
SliderBox(
child: FlutterLogo(
colors: Colors.blue,
))
];
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _animateSlider());
}
void _animateSlider() {
Future.delayed(Duration(seconds: 2)).then((_) {
int nextPage = _controller.page.round() + 1;
if (nextPage == _list.length) {
nextPage = 0;
}
_controller
.animateToPage(nextPage, duration: Duration(seconds: 1), curve: Curves.linear)
.then((_) => _animateSlider());
});
}
#override
Widget build(BuildContext context) {
PageIndicatorContainer container = new PageIndicatorContainer(
pageView: new PageView(
children: _list,
controller: _controller,
),
length: _list.length,
padding: EdgeInsets.fromLTRB(10, 40, 10, 10),
indicatorSpace: 10,
indicatorColor: Colors.grey[350],
indicatorSelectorColor: Colors.grey,
);
return Stack(
children: <Widget>[
Container(color: Colors.grey[100], height: double.infinity),
Container(color: Colors.white, child: container, margin: EdgeInsets.only(bottom: 50)),
],
);
}
}
class SliderBox extends StatelessWidget {
final Widget child;
const SliderBox({Key key, this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(padding: EdgeInsets.all(10), child: child);
}
}