i an trying to design the add to cart button when user click on 'add to cart' the 'add to cart' button hides and ListTile appear that is on back of button and listtile have three things title , leading and trailing everything is works like i want but issue is i want the same size of list tile as button size so it won't grow when listtile appear on screen
here is the video to clear what i mean
https://youtu.be/Bq2mrc5ao94
here is my code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int total=0;
bool cartbuttoncheck=true;
bool listbool=false;
IconData delete_icon=Icons.remove;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Button"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Visibility(
visible: listbool,
child: Container(
color: Colors.green,
child: SizedBox(
height: 50.0,
width: 160.0,
child: ListTile(
title: Center(child: Text(total.toString(),style: TextStyle(fontStyle: FontStyle.italic,color: Colors.white),)),
leading: IconButton(
icon: Icon(delete_icon,color: Colors.white,),
onPressed: (){
setState(() {
if(total==2)
{
print("i am 2");
delete_icon=Icons.delete;
total--;
}
else if(total==1 && delete_icon==Icons.delete)
{
total=0;
listbool=false;
cartbuttoncheck=true;
}
if(total>2)
{
// delete_icon=Icons.remove;
total--;
}
});
},
),
trailing: IconButton(
icon: Icon(Icons.add,color: Colors.white),
onPressed: (){
setState(() {
if(total==1 && delete_icon==Icons.delete)
{
delete_icon=Icons.remove;
}
total++;
});
},
),
),
),
),
),
Stack(
alignment: Alignment.center,
children: <Widget>[
Visibility(
visible: cartbuttoncheck,
child: RaisedButton(
onPressed: (){
setState(() {
cartbuttoncheck=false;
listbool=true;
if(total==0)
{
total++;
delete_icon=Icons.delete;
}
});
},
child: Text("Add to Cart",style: TextStyle(color: Colors.white),),
color: Colors.green,
),
),
],
),
],
),
),
),
);
}
}
Just like the ListTile uses a SizedBox with a defined size you can do the same to the RaisedButton
SizedBox(
height: 50.0,
width: 160.0,
child: RaisedButton(
onPressed: (){
setState(() {
cartbuttoncheck=false;
listbool=true;
if(total==0){
total++;
delete_icon=Icons.delete;
}
});
},
child: Text("Add to Cart",style: TextStyle(color: Colors.white),),
color: Colors.green,
),
)
Having said that I believe it would be better to not use all of the widgets you use(Column, Stack, Visibility) and just change between the ListTile and the RaisedButton based on a bool value (listbool for example)
class _MyAppState extends State<MyApp> {
int total = 0;
bool listbool = false;
IconData delete_icon = Icons.remove;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Button"),
),
body: Center(
child: Container(
color: Colors.green,
height: 50.0,
width: 160.0,
child: listbool
? ListTile(
title: Center(
child: Text(
total.toString(),
style: TextStyle(
fontStyle: FontStyle.italic, color: Colors.white),
)),
leading: IconButton(
icon: Icon(
delete_icon,
color: Colors.white,
),
onPressed: () {
setState(() {
if (total == 2) {
print("i am 2");
delete_icon = Icons.delete;
total--;
} else if (total == 1 &&
delete_icon == Icons.delete) {
total = 0;
listbool = false;
}
if (total > 2) {
// delete_icon=Icons.remove;
total--;
}
});
},
),
trailing: IconButton(
icon: Icon(Icons.add, color: Colors.white),
onPressed: () {
setState(() {
if (total == 1 && delete_icon == Icons.delete) {
delete_icon = Icons.remove;
}
total++;
});
},
),
)
: RaisedButton(
onPressed: () {
setState(() {
listbool = true;
if (total == 0) {
total++;
delete_icon = Icons.delete;
}
});
},
child: Text(
"Add to Cart",
style: TextStyle(color: Colors.white),
),
color: Colors.green,
),
),
),
),
);
}
}
Now you have a container of a specific size of color green in the center and based on the value of the listbool it changes its child to the ListTile and RaisedButton
UPDATE
class _MyAppState extends State<MyApp> {
double width; //the width of the RaisedButton based on the text
double height; //the heightof the RaisedButton based on the text
EdgeInsets myPadding = EdgeInsets.all(8); //a known padding to use in the RaisedButton
#override
void initState(){
super.initState();
TextSpan longestParagraphTest = TextSpan(
text: "Add to Cart",
style: TextStyle(color: Colors.white, fontSize: 14),
);
TextPainter _textPainter = TextPainter(text: longestParagraphTest, textDirection: TextDirection.ltr, maxLines: 1)..layout(minWidth: 0.0, maxWidth: double.infinity);
width = _textPainter.width + 16; //this is the padding
height = _textPainter.height + 16; //this is the padding
}
int total = 0;
bool listbool = false;
IconData delete_icon = Icons.remove;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Button"),
),
body: Center(
child: Container(
duration: const Duration(milliseconds: 300),
color: Colors.green,
height: height,
width: width, //now you know the size of the RaisedButton and use it in both the ListTile and RaisedButton
child: listbool
? ListTile(
title: Center(
child: Text(
total.toString(),
style: TextStyle(
fontStyle: FontStyle.italic, color: Colors.white),
)),
leading: IconButton(
icon: Icon(
delete_icon,
color: Colors.white,
),
onPressed: () {
setState(() {
if (total == 2) {
print("i am 2");
delete_icon = Icons.delete;
total--;
} else if (total == 1 &&
delete_icon == Icons.delete) {
total = 0;
listbool = false;
}
if (total > 2) {
// delete_icon=Icons.remove;
total--;
}
});
},
),
trailing: IconButton(
icon: Icon(Icons.add, color: Colors.white),
onPressed: () {
setState(() {
if (total == 1 && delete_icon == Icons.delete) {
delete_icon = Icons.remove;
}
total++;
});
},
),
)
: RaisedButton(
padding: myPadding,
onPressed: () {
setState(() {
listbool = true;
if (total == 0) {
total++;
delete_icon = Icons.delete;
}
});
},
child: Text(
"Add to Cart",
maxLines: 1,
style: TextStyle(color: Colors.white, fontSize: 14),
),
color: Colors.green,
),
),
),
),
);
}
}
And you will get something like this because the button size is too small for a ListTile to show 2 icons and a text
I have 2 recommendations if you want to keep it this way, in the initState change the minWidth in layout(minWidth: 0.0, maxWidth: double.infinity); to a greater value (let's say 100.0) just to be sure in case your minWidth is too small it changes to 100 to fit the ListTile. My second recommendation is to use an AnimatedContainer and change the size based on the widget displayed
AnimatedContainer(
duration: const Duration(milliseconds: 300),
color: Colors.green,
height: listbool ? 50.0 : height, //it animates to a a better size to fit the listTile
width: listbool ? 200.0 : width, //it animates to a a better size to fit the listTile
child: listbool ? ListTile(...) : RaisedButton(...),
),
where height and width are the values calculated in the initState. It will do a smooth animation between the 2 sizes (just like your video but smoother and cleaner)
Related
I am developing a task book, most of the tasks I have implemented but there is one that I can't solve, when I add a task it is added to the end of the list, and I want it to be at the top of the list. And also there is a problem when the theme changes, I want that if the theme is black, the text "Tasks +" were white and vice versa. Here is my code:
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(
home: App(),
));
}
class ListItem{
String todoText;
bool todoCheck;
ListItem(this.todoText, this.todoCheck);
}
class _strikeThrough extends StatelessWidget{
final String todoText;
final bool todoCheck;
_strikeThrough(this.todoText, this.todoCheck) : super();
Widget _widget(){
if(todoCheck){
return Text(
todoText,
style: TextStyle(
fontSize: 22.0,
),
);
}
else{
return Text(
todoText,
style: TextStyle(
fontSize: 22.0
),
);
}
}
#override
Widget build(BuildContext context){
return _widget();
}
}
const Color ColorTextW = Colors.black;
class App extends StatefulWidget{
#override
AppState createState(){
return AppState();
}
}
final ValueNotifier<ThemeMode> _notifier = ValueNotifier(ThemeMode.light);
late Color ColorType = Colors.black;
class AppState extends State<App> {
bool valText = true;
var counter = 0;
var IconsType = Icons.wb_sunny ;
late Color ColorType = Colors.black;
var textController = TextEditingController();
var popUpTextController = TextEditingController();
List<ListItem> WidgetList = [];
#override
void dispose() {
textController.dispose();
popUpTextController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<ThemeMode>(
valueListenable: _notifier,
builder: (_, mode, __) {
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: mode, // Decides which theme to show, light or dark.
home: Scaffold(
appBar: AppBar(
title: Text("Список задач"),
actions: <Widget>[
IconButton(
icon: Icon(IconsType,color : ColorType
),
onPressed:() =>
{
if (_notifier.value == ThemeMode.light) {
_notifier.value = ThemeMode.dark,
IconsType = Icons.dark_mode,
ColorType = Colors.white,
} else
{
_notifier.value = ThemeMode.light,
IconsType = Icons.wb_sunny,
ColorType = Colors.black,
}
}
)
],
//backgroundColor: Colors.orange[500],
iconTheme: IconThemeData(
color: Colors.white
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
"Tasks",
style: TextStyle(
fontSize: 70.0,
fontWeight: FontWeight.bold,
color: ColorTextW,
),
),
IconButton(
color: Colors.black,
iconSize: 70,
constraints: const BoxConstraints(),
padding: EdgeInsets.fromLTRB(30.0, 10.0, 30, 10.0),
icon: const Icon(Icons.add_outlined),
onPressed: () {
if (textController.text.replaceAll(" ", "").isNotEmpty) {
WidgetList.add(
new ListItem(textController.text.replaceAll(" ", ""), false));
setState(() {
valText = true;
textController.clear();
});
}
else
{
setState(() {
valText = false;
});
}
},
)
],
),
),
Container(
width: MediaQuery
.of(context)
.size
.height * 0.45,
child: TextField(
style: TextStyle(
fontSize: 22.0,
//color: Theme.of(context).accentColor,
),
controller: textController,
cursorWidth: 5.0,
autocorrect: true,
autofocus: true,
//onSubmitted: ,
),
),
Align(
child:
(valText == false) ?
Align(child: Text(("Задача пустая"),
style: TextStyle(
fontSize: 25.0, color: Colors.red)),
alignment: Alignment.center) :
Align(child: Text((""),),
alignment: Alignment.center)),
Expanded(
child: ReorderableListView(
children: <Widget>[
for(final widget in WidgetList)
GestureDetector(
key: Key(widget.todoText),
child: Dismissible(
key: Key(widget.todoText),
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
//key: ValueKey("Checkboxtile $widget"),
value: widget.todoCheck,
title: _strikeThrough(
widget.todoText, widget.todoCheck),
onChanged: (checkValue) {
//_strikethrough toggle
setState(() {
if (!checkValue!) {
widget.todoCheck = false;
}
else {
widget.todoCheck = true;
}
});
},
),
background: Container(
child: Icon(Icons.delete),
alignment: Alignment.centerRight,
color: Colors.redAccent,
),
direction: DismissDirection.endToStart,
movementDuration: const Duration(
milliseconds: 200),
onDismissed: (dismissDirection) { //Delete Todo
WidgetList.remove(widget);
},
),
)
],
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
var replaceWiget = WidgetList.removeAt(oldIndex);
WidgetList.insert(newIndex, replaceWiget);
});
},
),
)
],
),
)
);
}
);
}
}
In the onPressed method of your Add-IconButton do the following:
WidgetList.insert(0, new ListItem(textController.text.replaceAll(" ", ""), false));
This will insert the new item at the top of the existing list ^^
For instance: I have a main Icon so when you click on it, it opens a pop-up window with smaller icons/images to select from. So if you select one of the pictures from that pop-up it replaces the main Icon to that specific image.
I have spent hours trying to figure out how to replace icon images but nothing seems to work.
I have created an example (I have used flutter_speed_dial to make expandable buttons but it's not necessary). You can adjust it to your needs:
class _TestState extends State<Test> {
var fabIcon = Icons.expand_less;
var button1Icon = Icons.home;
var button2Icon = Icons.shop;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: SpeedDial(
icon: fabIcon,
backgroundColor: Color(0xFF801E48),
visible: true,
curve: Curves.bounceIn,
children: [
// FAB 1
SpeedDialChild(
child: Icon(button1Icon),
backgroundColor: Color(0xFF801E48),
onTap: () {
var temp = fabIcon;
setState(() {
fabIcon = button1Icon;
button1Icon = temp;
});
},
labelStyle: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16.0),
labelBackgroundColor: Color(0xFF801E48)),
// FAB 2
SpeedDialChild(
child: Icon(button2Icon),
backgroundColor: Color(0xFF801E48),
onTap: () {
var temp = fabIcon;
setState(() {
fabIcon = button2Icon;
button2Icon = temp;
});
},
labelStyle: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16.0),
labelBackgroundColor: Color(0xFF801E48))
],
),
),
);
}
}
Using showDialog(...) is the solution.
Hope this will help you and others.
you can look at this example:
import 'package:flutter/material.dart';
class IconDialogScreen extends StatefulWidget {
const IconDialogScreen({Key? key}) : super(key: key);
#override
State<IconDialogScreen> createState() => _IconDialogScreenState();
}
class _IconDialogScreenState extends State<IconDialogScreen> {
IconData icon = Icons.abc;
List<IconData> icons = [
Icons.abc,
Icons.person_add,
Icons.person,
Icons.person_remove,
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
onPressed: onIconClicked,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20,
children: [Icon(icon, size: 50), const Text("change icon")],
),
),
)
],
),
);
}
void onIconClicked() async {
IconData? _icon = await showDialog<IconData?>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Select one icon'),
content: Wrap(
spacing: 10,
runSpacing: 10,
children: icons.map<Widget>((e) {
return ElevatedButton(
onPressed: () {
Navigator.of(context).pop(e);
},
child: Icon(e, size: 50));
}).toList(),
),
);
},
);
if (_icon != null) {
setState(() {
icon = _icon;
});
}
}
}
I have been trying to fix this bug without success. I am trying to make a Gridview.builder widget that will hold the buttons for a calculator. I want the widget to fit on the lower part of the screen without overflowing. I tried to fix it by wrapping a Gridview into a Contianer or a ConstrainedBox with a certain height and width. However, the Gridview still keeps overflowing.
Here is my code for the Gridview:
Expanded(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: ScreenHeight(context) * .3,
maxHeight: ScreenHeight(context) * .4,
),
child: Container(
margin: EdgeInsets.only(
right: ScreenHeight(context) * .02,
left: ScreenHeight(context) * .02),
child: GridView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: buttons.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: getCrossAxisCount(context)),
itemBuilder: (BuildContext context, int index) {
// Clear Button
if (index == 0) {
return CalcButton(
onTap: () {
setState(() {
userInput = '';
calcAnswer = '';
});
},
buttonText: buttons[index],
color: Colors.blue[50],
textColor: Colors.black,
);
}
// +/- button
else if (index == 1) {
return CalcButton(
buttonText: buttons[index],
color: Colors.blue[50],
textColor: Colors.black,
);
}
// % Button
else if (index == 2) {
return CalcButton(
onTap: () {
setState(() {
userInput += buttons[index];
});
},
buttonText: buttons[index],
color: Colors.blue[50],
textColor: Colors.black,
);
}
// Delete Button
else if (index == 3) {
return CalcButton(
onTap: () {
setState(() {
userInput = userInput.substring(
0, userInput.length - 1);
});
},
buttonText: buttons[index],
color: Colors.blue[50],
textColor: Colors.black,
);
}
// Equal_to Button
else if (index == 18) {
return CalcButton(
onTap: () {
setState(() {
equalPressed();
});
},
buttonText: buttons[index],
color: Colors.orange[700],
textColor: Colors.white,
);
}
// other buttons
else {
return CalcButton(
onTap: () {
setState(() {
userInput += buttons[index];
});
},
buttonText: buttons[index],
color: isOperator(buttons[index])
? Colors.blueAccent
: Colors.white,
textColor: isOperator(buttons[index])
? Colors.white
: Colors.black,
);
}
},
),
),
),
),
Here is my code for CalcButton:
class CalcButton extends StatelessWidget {
final color;
final textColor;
final String buttonText;
final onTap;
CalcButton({
this.color,
this.textColor,
this.buttonText = "",
this.onTap,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(.3),
child: ClipRRect(
child: Container(
color: getCalcButtonColor(calcTheme,
buttonText), //gets calc button color from created method
child: Center(
child: Text(
buttonText,
style: TextStyle(
color: getButtonTxtColor(calcTheme,
buttonText), //gets button txt color from created method
fontSize: ScreenWidth(context) * .05,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
);
}
}
Here is how it looks:
Image 1
Image 2
Note that the ScreenHeight() and ScreenWidth() functions are functions that return the value of MediaQuery.of(context).size.width and MediaQuery.of(context).size.height
I have also tried to remove Expanded() and only use a ConstrainedBox, but the same thing still happens.
Any help will be much appreciated.
"When you select a color from the drop-down menu, the drop-down button widget’s title shows the selected color. Then, when you tap one of the four buttons, only that particular button’s background color will change to the selected background color." This is what I want to do it.
This picture shows the screen. There are 4 color options in the DropdownButton. In the beginning, I made maps for the get "Colors.black" etc. Then I wrote a function called "change" and this function for the color palettes.
But I confused about where to call this function. onPressed part of the RaisedButtons is empty right now. In the first RaisedButton,
_favIconColor = ;
That part will be equals to the new color. But I couldn't call the function anywhere.
This is my whole code:
import 'package:flutter/material.dart';
void main () => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String renk;
String decoration;
String x;
List<DropdownMenuItem> frommenuitems = [
DropdownMenuItem(child: Text('Black'),value: 'Black'),
DropdownMenuItem(child: Text('Green'),value: 'Green'),
DropdownMenuItem(child: Text('Orange'),value: 'Orange'),
DropdownMenuItem(child: Text('Blue'),value: 'Blue')];
final Map<String, int> renkmap = {
'Black': 0,
'Green': 1,
'Orange': 2,
'Blue': 3};
final Map<String, List> formulas = {
'0': [Colors.black],
'1': [Colors.green],
'2': [Colors.orange],
'3': [Colors.blue]};
void change(renk) {
int newcolor = renkmap[renk];
var result = formulas[newcolor];
}
Color _favIconColor = Colors.black; //for the set the RaisedButton color to the black in the beginning
#override
Widget build(BuildContext context) {
final TextStyle header = TextStyle (fontSize: 30, color: Colors.red[500], fontWeight: FontWeight.bold);
final TextStyle buttontext = TextStyle (fontSize: 25, color: Colors.white, fontWeight: FontWeight.bold);
return MaterialApp(
title: 'Color Changing',
home: Scaffold(
body: Container(
child: Column(
children: <Widget> [
Spacer(), //flex property
Text('Select a color', style: header),
DropdownButton(items: frommenuitems, hint: Text('Black', style: TextStyle(color: Colors.black)),
value: renk,
onChanged: (value) {
setState(() {
renk = value;
change(renk);
});
}
),
Spacer(),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(child: Text('Change Color', style: buttontext), color: _favIconColor,
onPressed: () {
setState(() {
//_favIconColor = ;
});
})),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(child: Text('Change Color', style: buttontext), color: Colors.black,
onPressed: () {
})),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(child: Text('Change Color', style: buttontext), color: Colors.black,
onPressed: () {})),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(child: Text('Change Color', style: buttontext), color: Colors.black,
onPressed: () {}))
],
),
),
),
);}}
you should do this
_favIconColor = Colors.black;
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(child: Text('Change Color', style: buttontext), color: _favIconColor,
onPressed: () {
setState(() {
_favIconColor =newColor ;// the one selected from the drop down menu
});
})),
the issue here is that every time you change the color all the Buttons will have the same color ( because of the setState ) if that ok for you ,
in case you want that every button has its own color you should consider using an array to know which button should change the color
Ps : i don't know what is your change function is used for !! it has no effect here unless it returns the color value or code depends on how you want to use it
I'm sure there are lots of better solutions with more clean codes. But for a temporary workaround, you can try this:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String renk;
String decoration;
String x;
List<DropdownMenuItem> frommenuitems = [
DropdownMenuItem(child: Text('Black'), value: 'Black'),
DropdownMenuItem(child: Text('Green'), value: 'Green'),
DropdownMenuItem(child: Text('Orange'), value: 'Orange'),
DropdownMenuItem(child: Text('Blue'), value: 'Blue')
];
final Map<String, int> renkmap = {
'Black': 0,
'Green': 1,
'Orange': 2,
'Blue': 3
};
final Map<String, List> formulas = {
'0': [Colors.black],
'1': [Colors.green],
'2': [Colors.orange],
'3': [Colors.blue]
};
Color _favIconColor = Colors.black;
List<Color> _favIconColorList = [
Colors.black,
Colors.black,
Colors.black,
Colors.black
];
int selectedIndex = -1;
//for the set the RaisedButton color to the black in the beginning
#override
Widget build(BuildContext context) {
void change(renk) {
int newcolor = renkmap[renk];
var result = formulas[newcolor.toString()][0];
/**
* In here, you need to convert int to string, and take the first of the Color list
*/
// In here, if an item selected, then only it should be change.
if (selectedIndex == -1) {
_favIconColorList[0] = result;
_favIconColorList[1] = result;
_favIconColorList[2] = result;
_favIconColorList[3] = result;
} else {
_favIconColorList[selectedIndex] = result;
}
}
final TextStyle header = TextStyle(
fontSize: 30, color: Colors.red[500], fontWeight: FontWeight.bold);
final TextStyle buttontext = TextStyle(
fontSize: 25, color: Colors.white, fontWeight: FontWeight.bold);
return MaterialApp(
title: 'Color Changing',
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
Spacer(), //flex property
Text('Select a color', style: header),
DropdownButton(
items: frommenuitems,
hint: Text("Color", style: TextStyle(color: _favIconColor)),
value: renk,
onChanged: (value) {
setState(() {
renk = value;
change(renk);
});
}),
Spacer(),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(
child: Text('Change Color', style: buttontext),
color: _favIconColorList[0],
onPressed: () {
setState(() {
selectedIndex = 0;
//_favIconColor = ;
});
})),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(
child: Text('Change Color', style: buttontext),
color: _favIconColorList[1],
onPressed: () {
setState(() {
selectedIndex = 1;
});
})),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(
child: Text('Change Color', style: buttontext),
color: _favIconColorList[2],
onPressed: () {
setState(() {
selectedIndex = 2;
});
})),
ButtonTheme(
minWidth: 2000.0,
height: 100.0,
child: RaisedButton(
child: Text('Change Color', style: buttontext),
color: _favIconColorList[3],
onPressed: () {
setState(() {
selectedIndex = 3;
});
}))
],
),
),
),
);
}
}
You can define a _favIconColorList to store for each ButtonTheme item's color and a selectedIndex with a default value, -1. If it set to an index of ButtonTheme list by tapping one of them, then the color will set only the selected ButtonTheme. Otherwise, all of the ButtonTheme list will be change.
I have 2 screens and I am trying to achieve understand how to achieve page state. For example, in the below screen, I have 4 options, and all of them take the user to the same screen the only difference is API getting called for each of them is different to build a list. I am trying to handle back arrow action, and that's where I am having issues.
Use Case -
When user is on screen 2 he is playing the song, now on back press song continues to play. Now when the user again chooses the same option from screen 1 I want to show the same list without reload and selection. If user selects any other option it should behave normal, which is working.
Solution -
I can hardcode loaded songs list and send back to screen 1 and then again get that back with the selection but this will take lot of resources.
AutomaticKeepAliveClientMixin i tried this tutorial but that didn't help or kept state active.
PageStorage is 3rd option i saw but i found majority of the time this is being used on app where we have a tab.
Screen 1 -
class Dashboard extends StatefulWidget {
int playingId;
Dashboard({this.playingId});
#override
_DashboardState createState() => _DashboardState(playingId);
}
class _DashboardState extends State<Dashboard> {
String appname;
int playingId = 0;
_DashboardState(this.playingId);
// print('${playingId}');
#override
void initState() {
appname="";
// playingId=0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: AppColors.darkBlue,
centerTitle: true,
title: Text("Tirthankar",
style: TextStyle(color: Colors.white),),
),
backgroundColor: AppColors.styleColor,
body: Column(
children: <Widget>[
// SizedBox(height: 5),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CustomGridWidget(
child: Icon(
Icons.file_download,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/bhaktambar.png',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
label: "Bhakti",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Kids",playingId: playingId,),
),
);
},
),
CustomGridWidget(
child: Icon(
Icons.file_download,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/bhaktambar.png',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
label: "Kids",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Kids",playingId: playingId,),
),
);
},
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CustomGridWidget(
child: Icon(
Icons.favorite,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/kids.jpg',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
label: "Favorite",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Songs"),
),
);
},
),
Align(
alignment: Alignment.center,
child: CustomGridWidget(
child: Icon(
Icons.book,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/vidyasagar.jpg',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Bhajan"),
),
);
},
),
),
],
),
),
]
),
);
}
Material boxTiles(IconData icon, String name){
return Material(
color: AppColors.mainColor,
elevation: 14.0,
shadowColor: AppColors.styleColor,
borderRadius: BorderRadius.circular(24.0),
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child:Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
onTap: (){print("tapped"); /* or any action you want */ },
child: Container(
width: 130.0,
height: 10.0,
color : Colors.transparent,
), // container
), //
//Text
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(name,
style:TextStyle(
color: AppColors.styleColor,
fontSize: 20.0,
)
),
),
//Icon
Material(
color: AppColors.styleColor,
borderRadius: BorderRadius.circular(50.0),
child: Padding(padding: const EdgeInsets.all(10.0),
child: Icon(icon, color: AppColors.lightBlue,size: 30,),
),
),
],
)
],
)
),
)
);
}
}
Screen 2 -
// import 'dart:js';
class ListPage extends StatefulWidget {
String appname;
int playingId;
ListPage({this.appname,this.playingId});
#override
_ListPageState createState() => _ListPageState(appname,playingId);
}
class _ListPageState extends State<ListPage>
with SingleTickerProviderStateMixin,AutomaticKeepAliveClientMixin<ListPage> {
String appname;
int playingId;
bool isPlaying = false;
_ListPageState(this.appname,playingId);
// List<MusicModel> _list1;
List<MusicData> _list;
var _value;
int _playId;
int _songId;
String _playURL;
bool _isRepeat;
bool _isShuffle;
bool _isFavorite;
String _startTime;
String _endTime;
AnimationController _controller;
Duration _duration = new Duration();
Duration _position = new Duration();
final _random = new Random();
AudioPlayer _audioPlayer = AudioPlayer();
#override
void initState() {
_playId = 0;
// _list1 = MusicModel.list;
this._fileUpdate();
// _list = _list1;
_controller =
AnimationController(vsync: this, duration: Duration(microseconds: 250));
_value = 0.0;
_startTime = "0.0";
_endTime = "0.0";
_isRepeat = false;
_isShuffle = false;
_isFavorite = false;
_audioPlayer.onAudioPositionChanged.listen((Duration duration) {
setState(() {
// _startTime = duration.toString().split(".")[0];
_startTime = duration.toString().split(".")[0];
_duration = duration;
// _position = duration.toString().split(".")[0];
});
});
_audioPlayer.onDurationChanged.listen((Duration duration) {
setState(() {
_endTime = duration.toString().split(".")[0];
_position = duration;
});
});
_audioPlayer.onPlayerCompletion.listen((event) {
setState(() {
isPlaying = false;
_position = _duration;
if (_isRepeat) {
_songId = _songId;
} else {
if (_isShuffle) {
var element = _list[_random.nextInt(_list.length)];
_songId = element.id;
} else {
_songId = _songId + 1;
}
}
_player(_songId);
});
});
super.initState();
}
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: AppColors.mainColor,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => Dashboard(playingId: _songId),
),
);
},
),
title: Text(
appname,
style: TextStyle(color: AppColors.styleColor),
),
),
backgroundColor: AppColors.mainColor,
body: Stack(
children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CustomButtonWidget(
child: Icon(
Icons.favorite,
color: AppColors.styleColor,
),
size: 50,
onTap: () {},
),
CustomButtonWidget(
image: 'assets/logo.jpg',
size: 100,
borderWidth: 5,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => DetailPage(),
),
);
},
),
CustomButtonWidget(
child: Icon(
Icons.menu,
color: AppColors.styleColor,
),
size: 50,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => HomePage(),
),
);
},
)
],
),
),
slider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(
_isRepeat ? Icons.repeat_one : Icons.repeat,
color: _isRepeat ? Colors.brown : AppColors.styleColor,
),
onPressed: () {
if (_isRepeat) {
_isRepeat = false;
} else {
_isRepeat = true;
}
},
),
IconButton(
icon: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
color: AppColors.styleColor,
),
onPressed: () {
if (isPlaying) {
_audioPlayer.pause();
setState(() {
isPlaying = false;
});
} else {
if (!isPlaying){
_audioPlayer.resume();
setState(() {
isPlaying = true;
});
}
}
}),
IconButton(
icon: Icon(
Icons.stop,
color: AppColors.styleColor,
),
onPressed: () {
if (isPlaying){
_audioPlayer.stop();
setState(() {
isPlaying = false;
_duration = new Duration();
});
}
// isPlaying = false;
}),
IconButton(
icon: Icon(
Icons.shuffle,
color:
_isShuffle ? Colors.brown : AppColors.styleColor,
),
onPressed: () {
if (_isShuffle) {
_isShuffle = false;
} else {
_isShuffle = true;
}
}),
],
),
),
Expanded(
//This is added so we can see overlay else this will be over button
child: ListView.builder(
physics:
BouncingScrollPhysics(), //This line removes the dark flash when you are at the begining or end of list menu. Just uncomment for
// itemCount: _list.length,
itemCount: _list == null ? 0 : _list.length,
padding: EdgeInsets.all(12),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
_songId = index;
_player(index);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
//This below code will change the color of sected area or song being played.
decoration: BoxDecoration(
color: _list[index].id == _playId
? AppColors.activeColor
: AppColors.mainColor,
borderRadius: BorderRadius.all(
Radius.circular(20),
),
),
//End of row color change
child: Padding(
padding: const EdgeInsets.all(
16), //This will all padding around all size
child: Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, //This will allign button to left, else button will be infront of name
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_list[index].title,
style: TextStyle(
color: AppColors.styleColor,
fontSize: 16,
),
),
Text(
_list[index].album,
style: TextStyle(
color: AppColors.styleColor.withAlpha(90),
fontSize: 16,
),
),
],
),
IconButton(
icon: Icon(_isFavorite
? Icons.favorite
: Icons.favorite_border),
onPressed: () {
if (_isFavorite) {
_isFavorite = false;
} else {
_isFavorite = true;
}
})
//Diabled Play button and added fav button.
// CustomButtonWidget(
// //This is Play button functionality on list page.
// child: Icon(
// _list[index].id == _playId
// ? Icons.pause
// : Icons.play_arrow,
// color: _list[index].id == _playId
// ? Colors.white
// : AppColors.styleColor,
// ),
// size: 50,
// isActive: _list[index].id == _playId,
// onTap: () async {
// _songId = index;
// _player(index);
// },
// )
],
),
),
),
);
},
),
)
],
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.mainColor.withAlpha(0),
AppColors.mainColor,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
)),
),
)
],
),
// floatingActionButton: FloatingActionButton(
// child: Icon(Icons.music_note),
// onPressed: () async { // String filePath = await FilePicker.getFilePath();
// int status = await _audioPlayer.play("https://traffic.libsyn.com/voicebot/Jan_Konig_on_the_Jovo_Open_Source_Framework_for_Voice_App_Development_-_Voicebot_Podcast_Ep_56.mp3");
// if (status == 1){
// setState(() {
// isPlaying = true;
// });
// }
// },
// )
);
}
Widget slider() {
return Slider(
activeColor: AppColors.styleColor,
inactiveColor: Colors.lightBlue,
value: _duration.inSeconds.toDouble(),
min: 0.0,
max: _position.inSeconds.toDouble(),
divisions: 10,
onChangeStart: (double value) {
print('Start value is ' + value.toString());
},
onChangeEnd: (double value) {
print('Finish value is ' + value.toString());
},
onChanged: (double value) {
setState(() {
seekToSecond(value.toInt());
value = value;
});
});
}
Future<Void> _fileUpdate() async {
String url =
"azonaws.com/input.json";
String arrayObjsText = "";
try {
eos.Response response;
Dio dio = new Dio();
response = await dio.get(url,options: Options(
responseType: ResponseType.plain,
),);
arrayObjsText = response.data;
print(response.data.toString());
} catch (e) {
print(e);
}
var tagObjsJson = jsonDecode(arrayObjsText)['tags'] as List;
this.setState(() {
_list = tagObjsJson.map((tagJson) => MusicData.fromJson(tagJson)).toList();
});
// return _list;
// print(_list);
}
Future<void> _player(int index) async {
if (isPlaying) {
if (_playId == _list[index].id) {
int status = await _audioPlayer.pause();
if (status == 1) {
setState(() {
isPlaying = false;
});
}
} else {
_playId = _list[index].id;
_playURL = _list[index].songURL;
_audioPlayer.stop();
int status = await _audioPlayer.play(_playURL);
if (status == 1) {
setState(() {
isPlaying = true;
});
}
}
} else {
_playId = _list[index].id;
_playURL = _list[index].songURL;
int status = await _audioPlayer.play(_playURL);
if (status == 1) {
setState(() {
isPlaying = true;
});
}
}
}
String _printDuration(Duration duration) {
String twoDigits(int n) {
if (n >= 10) return "$n";
return "0$n";
}
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
}
void seekToSecond(int second) {
Duration newDuration = Duration(seconds: second);
_audioPlayer.seek(newDuration);
}
}
PageStorage is 3rd option i saw but i found majority of the time this is being used on app where we have a tab.
You still can use this approach with what you want, with or without Tab/ bottom navigation bar.
PageViewClass
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin{
PageController _pageController;
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
void dispose() {
super.dispose();
_pageController?.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(), // so the user cannot scroll, only animating when they select an option
children: <Widget>[
Dashboard(playingId: 1, key: PageStorageKey<String>('MyPlayList'), pageController: _pageController), //or the name you want, but you need to give them a key to all the child so it can save the Scroll Position
ListPage(appname: "FirstButton",playingId: 1, key: PageStorageKey<String>('FirstButton'), pageController: _pageController),
ListPage(appname: "SecondButton",playingId: 1, key: PageStorageKey<String>('SecondButton'), pageController: _pageController),
ListPage(appname: "ThirdButton",playingId: 1, key: PageStorageKey<String>('ThirdButton'), pageController: _pageController),
ListPage(appname: "FourthButton",playingId: 1, key: PageStorageKey<String>('FourthButton'), pageController: _pageController)
],
),
)
);
}
}
Now you pass the PageController to all children (add a key and PageController attribute to Screen 1 and 2) and in DashBoard (Screen 1) you can change the onTap of the buttons from the MaterialRoute to this
onTap: () => widget.pageController.jumpToPage(x), //where x is the index of the children of the PageView you want to see (between 0 and 4 in this case)
In Screen 2 you wrap all your Scaffold with a WillPopScope so when the user tap back instead of closing the route it goes back to the Dashboard Widget at index 0
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
widget.pageController.jumpToPage(0); // Move back to dashboard (index 0)
return false;
}
child: Scaffold(...)
);
}
You can use other methods of PageController if you want some effects like animations (animateTo, nextPage, previousPage, etc.)