Instantly mark all checkboxes in dropdown without closing - flutter

I have a drop down list with checkboxes. When selecting the first element All EV's - all elements are selected. But visually, when I select the first element, I only mark the first element that I clicked on. I need to close and reopen the dropdown to see the checkmarks everywhere. I want that when the first element is selected, the checkboxes are also checked everywhere, but it does not close the drop-down list, but immediately sees the checked checkboxes. How to do it?
dropdown
class CheckboxDropdown extends StatefulWidget {
final List<String> items;
final SvgPicture? icon;
final double width;
const CheckboxDropdown({
Key? key,
required this.items,
this.icon,
required this.width,
}) : super(key: key);
#override
State<CheckboxDropdown> createState() => _CheckboxDropdown();
}
class _CheckboxDropdown extends State<CheckboxDropdown> {
String? selectedValue;
bool selected = false;
List _selectedTitles = [];
List selectedItems = [];
List _selectedTitlesIndex = [];
final GFCheckboxType type = GFCheckboxType.basic;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
_selectedTitles.add(widget.items[1]);
}
}
void _onItemSelect(bool selected, int index) {
if (selected == true) {
setState(() {
if (index == 0) {
_selectedTitles = List.from(widget.items);
_selectedTitlesIndex =
List.generate(widget.items.length, (index) => index);
} else {
_selectedTitles.add(widget.items[index]);
_selectedTitlesIndex.add(index);
}
});
} else {
setState(() {
if (index == 0) {
_selectedTitles.clear();
_selectedTitlesIndex.clear();
} else {
_selectedTitles.remove(widget.items[index]);
_selectedTitlesIndex.remove(index);
}
});
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton2(
offset: const Offset(0, -12),
isExpanded: true,
items: List.generate(
widget.items.length,
(index) => DropdownMenuItem<String>(
value: widget.items[index],
child: Container(
decoration: BoxDecoration(
border: index == widget.items.length - 1
? null
: Border(
bottom: BorderSide(
color: Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: StatefulBuilder(
builder: (context, setStateSB) => GFCheckboxListTile(
value: _selectedTitles.contains(widget.items[index]),
onChanged: (bool selected) {
_onItemSelect(selected, index);
setStateSB(() {});
},
selected: selected,
title: Text(
widget.items[index],
style: constants.Styles.smallTextStyleWhite,
),
padding: const EdgeInsets.only(top: 12, bottom: 13),
margin: const EdgeInsets.only(right: 0, left: 0),
size: 22,
activeBgColor: constants.Colors.greyCheckbox,
activeBorderColor: constants.Colors.greyXMiddle,
inactiveBgColor: constants.Colors.greyCheckbox,
activeIcon: SvgPicture.asset(constants.Assets.checkboxIcon),
inactiveBorderColor: constants.Colors.greyXMiddle,
type: type,
),
),
),
),
),
hint: Row(
children: [
SvgPicture.asset(constants.Assets.carDropdown),
const SizedBox(width: 8),
_selectedTitles.length > 1
? const Text(
'Selected EV',
style: constants.Styles.xSmallLtStdDropDownTextStyleWhite,
)
: Text(
_selectedTitles.join().toString(),
style: constants.Styles.xSmallLtStdDropDownTextStyleWhite,
),
if (_selectedTitles.isEmpty)
const Text(
'Select EV',
style: constants.Styles.xSmallLtStdDropDownTextStyleWhite,
),
],
),
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
buttonHeight: 27,
dropdownMaxHeight: 185,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: constants.Colors.purpleMain,
),
color: constants.Colors.greyDark),
),
),
);
}
}
list
final List<String> carList = const [
"All EV's",
'Main EV',
'<EV2>',
];

Related

dropdown_button2 Search is Not Working with Class List

I have a dropdown button which I implemented using dropdown_button2 1.9.2.
I want to make the dropdown_button searchable.
I tried implementing it with my custom class but it won't work.
I have tried overriding the toString method of my class (as suggested in this answer) but it still won't work.
Please any fix or am I doing something wrong here?
This is the dropdown button
This is what it does when I try searching for an item in the dropdown.
This is my full code:
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
// ignore_for_file: public_member_api_docs, sort_constructors_first
class MyDropDownItems {
MyDropDownItems({
required this.text,
required this.value,
});
String text;
String value;
#override
String toString() {
return value;
}
}
class MoodScreen extends StatefulWidget {
const MoodScreen({super.key});
#override
State<MoodScreen> createState() => _MoodScreenState();
}
class _MoodScreenState extends State<MoodScreen> {
final TextEditingController _textEditingController = TextEditingController();
// Initial Selected Value
String _dropdownvalue = ' ';
final _dropDownItems = [
MyDropDownItems(text: "Select Mood", value: " "),
MyDropDownItems(text: "Mood 1", value: "happy"),
MyDropDownItems(text: "Mood 2", value: "Sad"),
MyDropDownItems(text: "Mood 3", value: "Confused"),
MyDropDownItems(text: "Mood 4", value: "Lonely"),
MyDropDownItems(text: "Mood 5", value: "Excited"),
MyDropDownItems(text: "Mood 6", value: "Wealthy"),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 300,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.green, width: 4),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2(
searchController: _textEditingController,
searchInnerWidget: Padding(
padding: const EdgeInsets.only(
top: 8,
bottom: 4,
right: 8,
left: 8,
),
child: TextFormField(
controller: _textEditingController,
decoration: const InputDecoration(
isDense: true,
hintText: 'Select mood...',
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
),
),
),
searchMatchFn: (item, searchValue) {
return (item.value.toString().contains(searchValue));
},
onMenuStateChange: (isOpen) {
if (!isOpen) {
_textEditingController.clear();
}
},
value: _dropdownvalue,
isExpanded: true,
iconSize: 36,
iconEnabledColor: Colors.green,
icon: const Icon(
Icons.arrow_drop_down_circle,
),
items: _dropDownItems.map((MyDropDownItems networkNumbers) {
return DropdownMenuItem(
value: networkNumbers.value,
child: Text(networkNumbers.text),
);
}).toList(),
onChanged: (value) {
setState(() {
_dropdownvalue = value!;
});
},
),
),
),
const SizedBox(height: 20),
Text("The mood is: $_dropdownvalue"),
],
),
),
);
}
}
If you really want to search by the display name, then searchMatchFn should be as following
searchMatchFn: (item, searchValue) {
final myItem = _dropDownItems.firstWhere((element) => element.value == item.value);
return myItem.text.contains(searchValue);
}
or if you need search based on value and text, then
searchMatchFn: (item, searchValue) {
final myItem = _dropDownItems.firstWhere((element) => element.value == item.value);
return myItem.text.contains(searchValue) || item.value.toString().contains(searchValue);
}

Checkbox doesn't change when clicked in dropdownbutton

I am using DropdownButton and I am facing the following issue. I'm using a checkbox in elements, but when I click on an element, I don't get a checkmark indicating that the checkbox has been clicked. As a result, I need to close and reopen it, and then I will see the changes that were clicked on the "checkbox". The second problem is that when I select one element, all elements are selected for me. As a final result, I need to get so that I can select an element and the checkbox is immediately marked, if 2 elements are needed, then two, and so on. Tell me how to fix these problems, I will be grateful for the help?
dropdown
class DropdownWidget extends StatefulWidget {
List<String> items;
SvgPicture? icon;
double width;
DropdownWidget({
Key? key,
required this.items,
required this.icon,
required this.width,
}) : super(key: key);
#override
State<DropdownWidget> createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
String? selectedValue;
bool isChecked = false;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
selectedValue = widget.items[1];
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton2(
items: widget.items
.map((item) => DropdownMenuItem<String>(
value: item,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: constants.Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: Center(
child: Row(
children: [
if (item == selectedValue)
const SizedBox(
width: 0,
),
Expanded(
child: Text(
item,
style: constants.Styles.smallTextStyleWhite,
),
),
Checkbox(
checkColor: Colors.black,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
],
),
),
),
))
.toList(),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
buttonHeight: 27,
itemHeight: 47,
dropdownMaxHeight: 191,
dropdownWidth: 140,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: constants.Colors.purpleMain,
),
color: constants.Colors.greyDark,
),
selectedItemBuilder: (context) {
return widget.items.map(
(item) {
return Row(
children: [
widget.icon ?? const SizedBox(),
const SizedBox(width: 8),
Text(
item,
style: constants.Styles.bigBookTextStyleWhite,
),
],
);
},
).toList();
},
),
),
);
}
}
items
final List<String> items = const [
"All EV's",
'Main EV',
'<EV2>',
];
I hope this example explains the concept. For simplcity I made simple a new file, run it and see the results:
Then main idea in two lists, _checkList contain values of the CheckBox and _selectedList handles the main dropdown widget to show the selection.
Feel free to ask any questions and I'm happy to help
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const AnimationDemo(number: 5);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({Key? key, this.number = 2}) : super(key: key);
final int number;
#override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
late List<bool> _checkList;
late List<int> _selectedIndex;
bool _isOpen = false;
#override
void initState() {
_checkList = List.filled(widget.number, false);
_selectedIndex = <int>[];
super.initState();
}
List<DropDownItem> generateItems() {
var tmp = <DropDownItem>[];
for (var i = 0; i < _checkList.length; i++) {
tmp.add(DropDownItem(
isChecked: _checkList[i],
onChanged: (value) {
setState(() {
_checkList[i] = value!;
if (value && !_selectedIndex.contains(i)) {
_selectedIndex.add(i);
} else {
_selectedIndex.remove(i);
}
});
},
));
}
return tmp;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text((_selectedIndex.isEmpty)
? 'Nothing Selected'
: _selectedIndex.join(',')),
),
GestureDetector(
onTap: () {
setState(() {
_isOpen = !_isOpen;
});
},
child: const Icon(Icons.arrow_downward),
),
],
),
AnimatedOpacity(
opacity: (_isOpen) ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: generateItems(),
),
)
],
),
);
}
}
class DropDownItem extends StatelessWidget {
final bool isChecked;
final Function(bool?)? onChanged;
const DropDownItem({Key? key, this.onChanged, this.isChecked = false})
: super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(child: Text('Demo item')),
Checkbox(value: isChecked, onChanged: onChanged)
],
);
}
}
Here's how to achieve the Multiselect dropdown with DropdownButton2:
final List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
List<String> selectedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
hint: Align(
alignment: AlignmentDirectional.center,
child: Text(
'Select Items',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: item,
//disable default onTap to avoid closing menu when selecting an item
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item)
: selectedItems.add(item);
//This rebuilds the StatefulWidget to update the button's text
setState(() {});
//This rebuilds the dropdownMenu Widget to update the check mark
menuSetState(() {});
},
child: Container(
height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
_isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
const SizedBox(width: 16),
Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
//Use last selected item as the current value so if we've limited menu height, it scroll to last item.
value: selectedItems.isEmpty ? null : selectedItems.last,
onChanged: (value) {},
buttonHeight: 40,
buttonWidth: 140,
itemHeight: 40,
itemPadding: EdgeInsets.zero,
selectedItemBuilder: (context) {
return items.map(
(item) {
return Container(
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
),
),
),
);
}
Also, I've added it as an example to the package doc "Example 4" so you can get back to it later.

Selected item not showing in DropdownButton in Flutter

I need your help. I'm making a DropdownButton and I'm facing the following problem - I can't see the items that are selected in the DropdownMenuItems. I do not understand what the problem is that nothing is displayed. I'm using the [getwidget][1] package it's a GFCheckboxListTile widget - which just adds a checkbox. Please tell me how can I fix this error?
dropdown
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:getwidget/getwidget.dart';
import 'package:joyn/constants/constants.dart' as constants;
class DropdownWidget extends StatefulWidget {
List<String> items;
SvgPicture? icon;
double width;
DropdownWidget({
Key? key,
required this.items,
required this.icon,
required this.width,
}) : super(key: key);
#override
State<DropdownWidget> createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
String? selectedValue;
bool selected = false;
final List _selectedTitles = [];
final List _selectedTitlesIndex = [];
final GFCheckboxType type = GFCheckboxType.basic;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
_selectedTitles.add(widget.items[1]);
}
}
void _onItemSelect(bool selected, int index) {
if (selected == true) {
setState(() {
_selectedTitles.add(widget.items[index]);
_selectedTitlesIndex.add(index);
});
} else {
setState(() {
_selectedTitles.remove(widget.items[index]);
_selectedTitlesIndex.remove(index);
});
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton(
items: List.generate(
widget.items.length,
(index) => DropdownMenuItem<String>(
value: widget.items[index],
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: constants.Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: GFCheckboxListTile(
value: _selectedTitles.contains(widget.items[index]),
onChanged: (bool selected) {
_onItemSelect(selected, index);
},
selected: selected,
title: Text(
widget.items[index],
style: constants.Styles.smallTextStyleWhite,
),
padding: const EdgeInsets.only(top: 14, bottom: 13),
margin: const EdgeInsets.only(right: 12, left: 2),
size: 22,
activeBgColor: constants.Colors.greyCheckbox,
activeBorderColor: Colors.red,
inactiveBgColor: constants.Colors.greyCheckbox,
activeIcon: SvgPicture.asset(constants.Assets.checkboxIcon),
inactiveBorderColor: constants.Colors.greyXMiddle,
type: type,
),
),
),
),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
itemHeight: 66,
selectedItemBuilder: (context) {
return _selectedTitles.map(
(item) {
return Row(
children: [
widget.icon ?? const SizedBox(),
const SizedBox(width: 8),
Text(
item,
style: constants.Styles.bigBookTextStyleWhite,
),
],
);
},
).toList();
},
),
),
);
}
}
While the UI is need to update on DropdownMenuItem you can wrap GFCheckboxListTile with StatefulBuilder.
child: Container(
child: StatefulBuilder(
builder: (context, setStateSB) => GFCheckboxListTile(
value: _selectedTitles.contains(widget.items[index]),
onChanged: (bool selected) {
_onItemSelect(selected, index);
setStateSB(() {}); /// we are using StatefulBuilder's setState
///add your logic then
setState(() {
selectedValue = widget.items[index];
});
},
DropdownMenuItem ontap is outSide the GFCheckboxListTile therefore onChanged is not calling. I am using onChanged from GFCheckboxListTile to update the selectedValue.

Set default value for dropdown button in flutter

I have a dropdown button which works fine, but when I try to set a default value it will fail with the following error:
'package:flutter/src/material/dropdown.dart': Failed assertion: line 620 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.
This is my dropdown button:
Widget changeWorkspace() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: DropdownButton<AssignedWorkspace>(
isExpanded: true,
hint: Text("SELECT WORKSPACE"),
value: selectedWorkspace,
onChanged: (dropdownValueSelected) {
setState(() {
selectedWorkspace = dropdownValueSelected;
});
},
items: workspaces != null && workspaces.length > 0
? workspaces.map((AssignedWorkspace workspace) {
return new DropdownMenuItem<AssignedWorkspace>(
value: workspace,
child: new Text(workspace.name,
style: new TextStyle(color: Colors.black)),
);
}).toList()
: null),
),
]);
});
}
I've tried to set the value of selectedWorkspace onInit as follows but it fails.
selectedWorkspace = new AssignedWorkspace(
id: userSettings.currentWorkspaceId,
name: userSettings.currentWorkspaceName);
Is there a way of setting a default value in a dropdown button?
import 'package:flutter/material.dart';
import '../config/app_theme.dart';
class DropdownWidget extends StatefulWidget {
final String title;
final List<String> items;
final ValueChanged<String> itemCallBack;
final String currentItem;
final String hintText;
DropdownWidget({
this.title,
this.items,
this.itemCallBack,
this.currentItem,
this.hintText,
});
#override
State<StatefulWidget> createState() => _DropdownState(currentItem);
}
class _DropdownState extends State<DropdownWidget> {
List<DropdownMenuItem<String>> dropDownItems = [];
String currentItem;
AppTheme appTheme;
_DropdownState(this.currentItem);
#override
void initState() {
super.initState();
for (String item in widget.items) {
dropDownItems.add(DropdownMenuItem(
value: item,
child: Text(
item,
style: TextStyle(
fontSize: 16,
),
),
));
}
}
#override
void didUpdateWidget(DropdownWidget oldWidget) {
if (this.currentItem != widget.currentItem) {
setState(() {
this.currentItem = widget.currentItem;
});
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
appTheme = AppTheme(Theme.of(context).brightness);
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 6),
child: Text(
widget.title,
style: appTheme.activityAddPageTextStyle,
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 15),
margin: EdgeInsets.only(top: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0, 2),
blurRadius: 10,
color: Color(0x19000000),
),
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
icon: appTheme.activityAddPageDownArrowSVG,
value: currentItem,
isExpanded: true,
items: dropDownItems,
onChanged: (selectedItem) => setState(() {
currentItem = selectedItem;
widget.itemCallBack(currentItem);
}),
hint: Container(
child: Text(widget.hintText, style: appTheme.hintStyle),
),
),
),
),
],
),
);
}
}
This is my dropDownWidget without optimization. It has currentItem. You could use it like:
DropdownWidget(
title: kStatus,
items: state.customerStepInfo.statusList,
currentItem: status,
hintText: kCommonPick,
itemCallBack: (String status) {
this.status = status;
},
)
You need implement "equals" in class AssignedWorkspace. I used equatable package.
Example class AssignedWorkspace
class AssignedWorkspace extends Equatable {
final String id;
final String name;
AssignedWorkspace(this.id, this.name);
#override
List<Object> get props => [id];
}
For me id of one of the element is null, once added id is made non-null issue got fixed.
I changed the value of the dropdown var to 1 initially
var _value = '1';
So when the dropdown button has to display its value it displays the one whose value I have set 1 as in the items list in DropDownButton
DropdownButton(
underline: Container(),
onChanged: (value) {
setState(() {
_value = value;
});
},
value: _value,
items: [
DropdownMenuItem(
value: "1",
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(MaterialCommunityIcons.devices),
SizedBox(width: 10),
Text(
"Consumption",
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
),
],
),
),
DropdownMenuItem(
value: "2",
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(MaterialCommunityIcons.solar_panel),
SizedBox(width: 10),
Text(
"Generation",
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
),
],
),
),
],
),
if you want to see only an initial value you can use hint text named parameter of drop down button and set a text widget. i dont know whether it is a good practice or not.

Button width not changing during animation

I'm trying to implement a button animation when on clicked it shrinks and shows the circular progress indicator (while loading) then expands and shows the result of the executed operation (in this case it is login).
The code idea came from this link. The design idea came from this link. Now I implemented this before and it worked exactly as it was supposed to. However when implementing it again here, on button pressed -> the person logs in successfully and the button changes color as per design an all. The only problem is that the button animation does not happen. I tried printing the values of the _loginButtonWidth and can actually see it decreasing and increasing as per design, but visually the width stays the same.
Code:
import 'package:flutter/material.dart';
import 'package:garuda_academy_app/Login/Authentication.dart';
import 'package:garuda_academy_app/Tools/FixedColors.dart';
import 'dart:async';
class LoginPage extends StatefulWidget {
LoginPage({this.auth, this.onLoggedIn});
#override
_LoginPageState createState() => _LoginPageState();
final BaseAuth auth;
final VoidCallback onLoggedIn;
}
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
// for device type
bool _isIos;
// text form field
String _userEmail = "";
String _userPassword = "";
final _formKey = GlobalKey<FormState>();
// for login button
int _loginButtonState = 0;
double _loginButtonWidth = double.maxFinite;
Color _loginButtonColor = primaryColor;
Color _loginButtonOutlineColor = primaryColor;
Color _loginButtonTextColor = secondaryColor;
GlobalKey _loginButtonKey = GlobalKey();
Animation _loginButtonAnimation;
AnimationController _loginButtonController;
Widget _loginButton() {
if (_loginButtonState == 0) {
return Text(
"Log In",
style: TextStyle(
color: _loginButtonTextColor,
fontSize: 20,
),
);
} else if (_loginButtonState == 1) {
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(secondaryColor),
);
} else if (_loginButtonState == 2) {
return Icon(
Icons.check,
color: _loginButtonTextColor,
);
} else if (_loginButtonState == 3) {
return Icon(
Icons.close,
color: _loginButtonTextColor,
);
} else if (_loginButtonState == 4) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.check,
color: _loginButtonTextColor,
),
Icon(
Icons.check,
color: transparent,
),
Text(
"Successful",
style: TextStyle(
color: _loginButtonTextColor,
),
),
],
);
} else if (_loginButtonState == 5) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.close,
color: _loginButtonTextColor,
),
Icon(
Icons.close,
color: transparent,
),
Text(
"Unsuccessful",
style: TextStyle(
color: _loginButtonTextColor,
),
),
],
);
}
}
bool _validateLoginAndSave() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return true;
}
return false;
}
_animateLoginButton() async {
String userId = "";
String errorMsg = "";
setState(() {
_loginButtonState = 1;
});
// animation
double initialWidth = _loginButtonKey.currentContext.size.width;
_loginButtonController =
AnimationController(duration: Duration(milliseconds: 300), vsync: this)
..addStatusListener((AnimationStatus status) async {
if (status == AnimationStatus.completed) {
// firebase signin
try {
userId = await widget.auth.signIn(_userEmail, _userPassword);
} catch (e) {
setState(() {
errorMsg = _isIos ? e.details : e.message;
print(errorMsg);
});
}
// loading timer
Timer(Duration(seconds: 1), () {
// set login state
_loginButtonState =
(userId.length > 0 && userId != null) ? 2 : 3;
// change colors
if (_loginButtonState == 2) {
_loginButtonColor = secondaryColor;
_loginButtonOutlineColor = successfulColor;
_loginButtonTextColor = successfulColor;
} else if (_loginButtonState == 3) {
_loginButtonColor = secondaryColor;
_loginButtonOutlineColor = unsuccessfulColor;
_loginButtonTextColor = unsuccessfulColor;
}
_loginButtonController.reverse();
});
} else if (status == AnimationStatus.dismissed) {
if (_loginButtonState == 2) {
_loginButtonState = 4;
} else if (_loginButtonState == 3) {
_loginButtonState = 5;
}
// minimal time before it is done
Timer(Duration(seconds: 1), () {
setState(() {
if (_loginButtonState == 4) widget.onLoggedIn();
// reset state
_loginButtonState = 0;
// reset colors
_loginButtonColor = primaryColor;
_loginButtonOutlineColor = primaryColor;
_loginButtonTextColor = secondaryColor;
});
});
}
});
_loginButtonAnimation =
Tween(begin: 0.0, end: 1.0).animate(_loginButtonController)
..addListener(() {
setState(() {
_loginButtonWidth = initialWidth -
((initialWidth - 80.0) * _loginButtonAnimation.value);
});
print("initial: " + initialWidth.toString());
print("current: " + _loginButtonWidth.toString());
});
_loginButtonController.forward();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
_isIos = Theme.of(context).platform == TargetPlatform.iOS;
return Scaffold(
resizeToAvoidBottomInset: false,
body: SingleChildScrollView(
child: Center(
child: Theme(
data: ThemeData(primaryColor: primaryColor),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.all(40),
child: Text(
"Log in to continue",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
),
Padding(
padding: EdgeInsets.only(bottom: 20, left: 40, right: 40),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
style: TextStyle(
fontSize: 20,
),
decoration: InputDecoration(
labelText: "Email Address",
labelStyle: TextStyle(fontSize: 20),
),
validator: (value) =>
value.isEmpty ? "Email cannot be empty" : null,
onSaved: (value) => _userEmail = value,
),
),
Padding(
padding: EdgeInsets.only(bottom: 20, left: 40, right: 40),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
obscureText: true,
style: TextStyle(
fontSize: 20,
),
decoration: InputDecoration(
labelText: "Password",
labelStyle: TextStyle(fontSize: 20),
),
validator: (value) =>
value.isEmpty ? "Password cannot be empty" : null,
onSaved: (value) => _userPassword = value,
),
),
Padding(
padding: EdgeInsets.only(bottom: 50, left: 40, right: 40),
child: Container(
height: 60,
width: _loginButtonWidth,
child: PhysicalModel(
color: transparent,
borderRadius: BorderRadius.circular(10.0),
child: RaisedButton(
elevation: 8.0,
color: _loginButtonColor,
key: _loginButtonKey,
shape: OutlineInputBorder(
borderSide: BorderSide(
color: _loginButtonOutlineColor,
),
borderRadius: BorderRadius.circular(10.0),
),
child: _loginButton(),
onPressed: () {
setState(() {
if (_loginButtonState == 0 &&
_validateLoginAndSave()) {
_animateLoginButton();
}
});
},
),
),
),
),
],
),
),
),
),
),
);
}
}
Button width stays the same when it is supposed to shrink:
Easy fix, add a Center or Align widget as a parent of your Container button.
Padding(
padding: EdgeInsets.only(bottom: 50, left: 40, right: 40),
child: Center(
child: Container(
height: 60,
width: _loginButtonWidth,
To get more info check Layout Behavior