Related
I have an application like this:
My aim is that when I press the eye icon next to the text "Hello", I want a box to open just below the text and write the German version of "Hello". So it will say "Hallo".
My purpose is to show the meaning of the word.
When I press the eye, I want to show the German of the word. How can I make a white box under the word Hello, that is, the box in which the German language will be written?
Codes:
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
class selamlasmaLearn extends StatelessWidget {
List <wordAndMeaning> wordsList = [wordAndMeaning("Hello", "Hallo"), wordAndMeaning("Go", "Gehen")];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) {
final double height = MediaQuery.of(context).size.height;
return CarouselSlider(
options: CarouselOptions(
height: height,
viewportFraction: 1.0,
enlargeCenterPage: false,
),
items: wordsList.map((wordAndMeaning word) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amber),
child: Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
word.word,
style: TextStyle(fontSize: 45, color: Colors.white),
),
SizedBox(width: 10,),
Icon(Icons.remove_red_eye_sharp, color: Colors.white, size: 25,), // <<<<<<<<<
],
),
),
);
},
);
}).toList(),
);
}
),
);
}
}
class wordAndMeaning {
String word;
String meaning;
wordAndMeaning(this.word, this.meaning);
}
I keep the word and its German in a list called wordsList.
Thanks for the help in advance.
You can convert the widget to StatefulWidget or use a ValueNotifier to control the preserve/notify the state visibility.
You can use Visibility widget or just if to show and hide German text.
class selamlasmaLearn extends StatefulWidget {
#override
State<selamlasmaLearn> createState() => _selamlasmaLearnState();
}
class _selamlasmaLearnState extends State<selamlasmaLearn> {
bool _showGerman = false;
List<wordAndMeaning> wordsList = [
wordAndMeaning("Hello", "Hallo"),
wordAndMeaning("Go", "Gehen")
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(builder: (context) {
final double height = MediaQuery.of(context).size.height;
return CarouselSlider(
options: CarouselOptions(
height: height,
viewportFraction: 1.0,
enlargeCenterPage: false,
),
items: wordsList.map((wordAndMeaning word) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amber),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(word.word,
style:
TextStyle(fontSize: 45, color: Colors.white)),
if (_showGerman) Text(word.meaning), //modify the way you want
],
),
const SizedBox(
width: 10,
),
IconButton(
icon: Icon(Icons.remove_red_eye_sharp),
color: Colors.white,
iconSize: 25,
onPressed: () {
setState(() {
_showGerman = !_showGerman;
});
},
),
],
),
);
},
);
}).toList(),
);
}),
);
}
}
Use the Tooltip widget
I'm emphasizing on the popup part in your question title. When using a Tooltip you ensure that your widgets do not shift position or jump when the Tooltip widget appear, as the example below illustrates.
Example code:
import 'package:flutter/material.dart';
class TooltipExample extends StatelessWidget {
const TooltipExample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Tooltip(
// Set the tooltip to trigger on a single tap, tapping outside the
// widget will make the tooltip disappear.
triggerMode: TooltipTriggerMode.tap,
// The message shown when the tooltip appears.
message: "Tooltip showing!",
// Consider adjusting this to your needs.
showDuration: const Duration(days: 1),
// The widget that must be clicked to show the tooltip.
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text("Hello"),
SizedBox(
width: 8,
),
Icon(Icons.visibility),
],
),
),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text("Cover me!"),
)
],
),
);
}
}
// Some code to run the above example, note the theme part that turns the
// tooltip white.
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
// Style the overall design of tooltips in the app in one place,
// or provide in each tooltip individually.
theme: ThemeData(
tooltipTheme: const TooltipThemeData(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(4),
),
),
textStyle: TextStyle(
backgroundColor: Colors.white,
color: Colors.black,
),
),
),
home: const Scaffold(
backgroundColor: Colors.amber,
body: TooltipExample(),
),
);
}
}
void main() => runApp(const App());
Here is how it looks:
Note that the Tooltip widget overlays whatever is below it. (instead of pushing it further down - like toggling the visibility of a normal widget in a row or column would have done)
I'm having trouble managing the status of my dropdowns,
I have two dropDowns, the items of the second are built based on the item selected in the first.
My problem is being to clean the second one when I change the option of the first one again.
I tried to set the value of the second to null at the time if I update the value of the first but even though it is still giving problem.
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobicar/app/stores/brand_store.dart';
import 'package:mobicar/app/stores/vehicle_store.dart';
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var brandStore = BrandStore();
var vehicleStore = VehicleStore();
var selectedBrand;
var selectedVehicle;
#override
void initState() {
brandStore.getBrands().then((value) {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _body(),
);
}
Column _body() {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () => _newItemDialog(),
child: Container(
margin: EdgeInsets.only(top: 5, right: 5),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: Text(
"new",
style: TextStyle(color: Colors.white),
),
),
),
],
),
Expanded(
child: ListView(
children: [Text("Supervisor, selecione a viatura")],
))
],
);
}
Future _newItemDialog() {
return showDialog(
barrierDismissible: false,
context: context,
builder: (context) => Container(
child: AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Novo Veiculo "),
IconButton(
icon: Icon(Icons.close),
onPressed: () => Navigator.pop(context))
],
),
content: Observer(builder: (_) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("assets/images/car.png"),
_dropdownButton(
hint: "Marca",
itemlist: _dropDownItemBrands,
type: 'brand'),
_dropdownButton(
hint: "Modelo",
itemlist: _dropDownItemVehicles,
type: 'vehicle'),
_dropdownButton(
hint: "Ano", itemlist: _dropDownItemBrands, type: ''),
],
);
}),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
));
}
_dropdownButton({required hint, required itemlist, required type}) {
return Container(
margin: EdgeInsets.only(top: 10),
child: DropdownButtonFormField(
hint: Text(hint),
items: itemlist() ?? [],
onChanged: (value) {
switch (type) {
case 'brand':
vehicleStore.getVehicles(value);
break;
case 'vehicle':
selectedVehicle = value;
break;
default:
}
},
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 8, right: 0, top: 0, bottom: 0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(4)),
),
),
);
}
List<DropdownMenuItem<int>> _dropDownItemBrands() {
List<DropdownMenuItem<int>> list = [];
if (brandStore.brandList.isNotEmpty) {
brandStore.brandList.forEach((element) {
list.add(
DropdownMenuItem<int>(
child: Text(
element.name,
style: TextStyle(color: Colors.black),
),
value: element.id,
),
);
});
return list;
} else {
return list;
}
}
List<DropdownMenuItem<int>> _dropDownItemVehicles() {
List<DropdownMenuItem<int>> list = [];
if (brandStore.brandList.isNotEmpty) {
vehicleStore.vehiclesList.forEach((element) {
list.add(
DropdownMenuItem<int>(
child: Text(
element.name,
style: TextStyle(color: Colors.black),
),
value: element.id,
),
);
});
return list;
} else {
return list;
}
}
}
When you want to change clean the values of the second dropdown menu on your UI, you have to use setState.
So,
I tried to set the value of the second to null at the time if I update
the value of the first but even though it is still giving problem.
Set it to null or an empty list [], but inside of setState:
//instead of yourSecondValue == null, use:
setState(() {
yourSecondValue == null;
});
This is assuming that setting it to null will actually solve the problem.
I'm doing a project in Flutter in which I'm getting live bit rate using a API and I'm getting my rate but can't display on my screen its say it null..! code below:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'coin_data.dart';
import 'dart:io' show Platform;
import 'networking.dart';
class PriceScreen extends StatefulWidget {
#override
_PriceScreenState createState() => _PriceScreenState();
}
class _PriceScreenState extends State<PriceScreen> {
BitNetwork bitNetwork = BitNetwork('$BitCoinURL/BTC/USD?apikey=$BitCoinKey');
int bitRate;
void getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
bitRate = temp.toInt();
print(bitRate);
}
String selectedCurrency = 'USD';`enter code here`
#override
Widget build(BuildContext context) {
getCurrentBitRate();
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),`enter code here`
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0),
child: Card(
color: Colors.lightBlueAccent,
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 28.0),
child: Text(
'1 BTC = $bitRate USD',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,
),
),
),
),
),
Container(
height: 150.0,
alignment: Alignment.center,
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.lightBlue,
child: Platform.isIOS ? iOSPicker() : androidDropdown()),
],
),
);
}
}
answer in console:
I/flutter (14181): 47131
I/flutter (14181): 47131
I/flutter (14181): 47129
output on screen is = 1 BTC = null USD. => ????
You need to wait for currency loading, wrap your widget to FutureBuilder:
Future<int> getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
return temp.toInt();
}
// build method
child: FutureBuilder<int>(
future: getCurrentBitRate(),
builder (context, snapshot) {
if (snapshot.hasData) {
final bitRate = snapshot.data;
return Column(
// Your column here.
);
}
return CircularProgressIndicator();
}
),
Also, you can find more information about how to work with async features here and read more about FutureBuilder here.
The problem is you're not awaiting getCurrentBitRate() and you are also calling it in your build method. Only UI code should be in the build method. What I recommend you do is override initState() and call it in there (Still can't await it, but it will be called before build);
#override
initState(){
getCurrentBitRate();
super.initState();
}
This will help with your issue, but it's not the best solution. I recommend looking up tutorials on some external state management system, such as BLoC, Provider and/or RxDart. This will make situations like this much easier to debug.
The bitRate value is null because you are calling it in build function & your method getCurrentBitRate() is an async method, which means that the method will wait to get the value but till then your build method would already finish rendering the widgets with bitRate value still null.
There are multiple ways to fix this but the one I would recommend is as follows:
Call your method getCurrentBitRate() in initState method & remove it from the build function as it is the first method that runs in your widget & use setState so that updated value of bitRate is shown in your widget.
class _PriceScreenState extends State<PriceScreen> {
BitNetwork bitNetwork = BitNetwork('$BitCoinURL/BTC/USD?apikey=$BitCoinKey');
int bitRate;
#override
void initState() {
super.initState();
getCurrentBitRate(); // Call it in initState & use setState
}
void getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
bitRate = temp.toInt();
print(bitRate);
if (mounted) { // <--- mounted property checks whether your widget is still present in the widget tree
setState((){}); // Will update the UI once the value is retrieved
}
}
String selectedCurrency = 'USD';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),`enter code here`
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0),
child: Card(
color: Colors.lightBlueAccent,
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 28.0),
child: Text(
'1 BTC = $bitRate USD',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
color: Colors.white,
),
),
),
),
),
Container(
height: 150.0,
alignment: Alignment.center,
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.lightBlue,
child: Platform.isIOS ? iOSPicker() : androidDropdown()),
],
),
);
}
}
It's null because when build() is called, getCurrentBitRate() didn't complete it's job yet.
For those operations FutureBuilder is one of the best widget. It just needs a future, and a builder to declare what to do after the data received.
// CHANGE TO FUTURE STYLE
Future<Int> getCurrentBitRate() async {
dynamic bitData = await bitNetwork.getData();
double temp = bitData['rate'];
bitRate = temp.toInt();
print(bitRate);
return bitRate;
}
Then change build structure to this
// DECLARE A FUTURE FOR getCurrentBitRate()
Future _future;
initState(){
_future = await getCurrentBitRate();
super.initState();
}
#override
Widget build(BuildContext context) {
// getCurrentBitRate(); REMOVE THIS LINE
return FutureBuilder(
future: _future,
builder: (context, snapshot) {
if(snapshot.hasData){
// YOUR DATA IS READY
double temp = snapshot.data['rate'];
// JUST CONTINUE REST OF ORIGINAL CODE BELOW
return Scaffold(
appBar: AppBar(
title: Text('Coin Ticker'),
),
...
}
}
);
I'm implementing this products app where a user can add a product from his wish list and add it to their cart. Once the user clicks the add to cart button, I want to delete the product from the screen and display a "success" Snackbar.
Since the products are loaded and displayed from FireBase Firestore, I delete the item and call setState({}); so that the list on the screen will be updated. The problem is that the SnackBar that comes right after setState({}); isn't shown.
I assume it is because the widget tree is rebuilt so the current state and context are "gone".
I tried finding some information online but haven't managed to find anything useful. I also tried to make a workaround with a bool flag that will be set once the user clicks the "Add to cart" button using and on setState the flag will be true one time to show the SnackBar when the widget tree is rebuilt and then turn the flag back to off but it didn't work as well.
What am I missing? How can I display a SnackBar after calling setState({})?
here is my code: (line in question marked with FIXME:)
slidable package installation
final GlobalKey _repaintBoundaryKey = GlobalKey();
final GlobalKey<ScaffoldState> _scaffoldKeyWishList = new GlobalKey<ScaffoldState>();
final Center _circularProgressIndicator = Center(
child: SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.lightGreen[800]),
)
),
);
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: FirebaseFirestore.instance.collection("Wishlists").doc(FirebaseAuth.instance.currentUser.uid).get(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> wishListSnapshot) {
if (wishListSnapshot.connectionState != ConnectionState.done) {
return _circularProgressIndicator;
} else if (!wishListSnapshot.hasData ||
0 == wishListSnapshot.data.data()['Wishlist'].length) {
return globals.emptyListErrorScreen(context, 'Wishlist');
}
int totalProducts = wishListSnapshot.data.data()['Wishlist'].length;
return Scaffold(
key: _scaffoldKeyWishList,
backgroundColor: Colors.lightGreen[800],
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white,
child: ListView.builder(
itemCount: totalProducts * 2,
shrinkWrap: true,
padding: const EdgeInsets.all(16),
itemBuilder: (BuildContext _context, int i) {
if (i >= 2 * totalProducts) {
return null;
}
if (i.isOdd) {
return Divider(
color: Colors.green,
thickness: 1.0,
);
}
var wishlistIdData = wishListSnapshot.data.data()['Wishlist'];
String productID = wishlistIdData[i ~/ 2];
return FutureBuilder(
future: FirebaseFirestore.instance.collection("Products").doc(productID).get(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> productSnapshot) {
if (wishListSnapshot.connectionState != ConnectionState.done || !productSnapshot.hasData) {
return _circularProgressIndicator;
}
var productData = productSnapshot.data.data()['Product'];
String prodName = productData['name'];
String prodPrice = productData['price'];
String prodDate = productData['date'];
return Slidable(
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.22,
direction: Axis.horizontal,
actions: <Widget>[
//add to cart
IconSlideAction(
caption: 'Add to cart',
color: Colors.transparent,
foregroundColor: Colors
.amberAccent,
icon: Icons.add_shopping_cart,
onTap: () async {
globals.userCart.add(
globals.Product(
productID,
FirebaseAuth.instance.currentUser.uid,
prodName,
double.parse(prodPrice),
prodDate,
[],
"",
"")
);
///removing product from wishlist
List toRemove = [];
toRemove.add(productID);
await FirebaseFirestore.instance
.collection('Wishlists')
.doc(FirebaseAuth.instance.currentUser.uid)
.get()
.then((value) async {
List<dynamic> list = List
.from(value
.data()['Wishlist']);
list
..removeWhere((e) =>
toRemove.contains(e));
await FirebaseFirestore.instance
.collection('Wishlists')
.doc(FirebaseAuth.instance.currentUser.uid)
.update(
{'Wishlist': list});
});
setState(() {
///to update the list on screen
});
//FIXME: snackbar not displayed after setState!
///showing snackbar upon completion
_scaffoldKeyWishList
.currentState
.showSnackBar(
SnackBar(
content: Text(
'Product Successfully Added to Cart!',
style: GoogleFonts
.lato(
fontSize: 13.0,
color: Colors
.white
),
),
behavior: SnackBarBehavior
.floating,
action: SnackBarAction(
label: 'Checkout',
textColor: Colors
.lime,
onPressed: () =>
showDialog(
context: context,
builder: (
BuildContext context) {
return CustomDialogBox();
},
),
),
)
);
},
),
],
child: ListTile(
title: Text(prodName,
style: GoogleFonts.lato(
fontSize: 18.0,
color: Colors.black,
),
),
subtitle: Text(prodPrice + "\$",
style: GoogleFonts.lato(
fontSize: 13.5,
color: Colors.grey,
),
),
visualDensity: VisualDensity
.adaptivePlatformDensity,
),
);
},
);
},
),
)
)
]
)
)
);
}
)
);
}
In my case I was calling setState method before build method complete process of building widgets.
You can face this error if you are showing snack bar or alert dialog before the completion of build method and in many other cases. so in such situation use below call back function.
WidgetsBinding.instance.addPostFrameCallback((_) {
// add your snackbar code here
});
or You can also use SchedulerBinding which does the same.
SchedulerBinding.instance.addPostFrameCallback((_) {
// add your code here of snackbar.
});
or you can try this too (I am not sure about this one)
if(mounted){
//add your code here of snackbar
}
CREDITS
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,
),
],
),
),