multiple selection inside the drop down menu in flutter - flutter

Hi In my App I have something like this.
where I have a dropdown which displaying 3 options, but is there any way I can select multiple options inside the dropdown in flutter? and to store the result of selected options inside the list?
or is it possible to do something like below in flutter?
Thanks.

Code:-
class CustomMultiselectDropDown extends StatefulWidget {
final Function(List<String>) selectedList;
final List<String> listOFStrings;
CustomMultiselectDropDown(
{required this.selectedList, required this.listOFStrings});
#override
createState() {
return new _CustomMultiselectDropDownState();
}
}
class _CustomMultiselectDropDownState extends State<CustomMultiselectDropDown> {
List<String> listOFSelectedItem = [];
String selectedText = "";
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Container(
margin: EdgeInsets.only(top: 10.0),
decoration:
BoxDecoration(border: Border.all(color: PrimeDentalColors.grey1)),
child: ExpansionTile(
iconColor: PrimeDentalColors.grey,
title: Text(
listOFSelectedItem.isEmpty ? "Select" : listOFSelectedItem[0],
style: GoogleFonts.poppins(
textStyle: TextStyle(
color: PrimeDentalColors.grey,
fontWeight: FontWeight.w400,
fontSize: 15.0,
),
),
),
children: <Widget>[
new ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.listOFStrings.length,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: EdgeInsets.only(bottom: 8.0),
child: _ViewItem(
item: widget.listOFStrings[index],
selected: (val) {
selectedText = val;
if (listOFSelectedItem.contains(val)) {
listOFSelectedItem.remove(val);
} else {
listOFSelectedItem.add(val);
}
widget.selectedList(listOFSelectedItem);
setState(() {});
},
itemSelected: listOFSelectedItem
.contains(widget.listOFStrings[index])),
);
},
),
],
),
);
}
}
class _ViewItem extends StatelessWidget {
String item;
bool itemSelected;
final Function(String) selected;
_ViewItem(
{required this.item, required this.itemSelected, required this.selected});
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Padding(
padding:
EdgeInsets.only(left: size.width * .032, right: size.width * .098),
child: Row(
children: [
SizedBox(
height: 24.0,
width: 24.0,
child: Checkbox(
value: itemSelected,
onChanged: (val) {
selected(item);
},
activeColor: PrimeDentalColors.blue,
),
),
SizedBox(
width: size.width * .025,
),
Text(
item,
style: GoogleFonts.poppins(
textStyle: TextStyle(
color: PrimeDentalColors.grey,
fontWeight: FontWeight.w400,
fontSize: 17.0,
),
),
),
],
),
);
}
}

You could achieve that by using a custom widget as a child of the DropdownMenuItem, where the custom widget would need to be stateful so it can handle it's own state to show a check mark or something. And it should have it's own onTap method, so the DropdownMenuItem onTap won't trigger and select the option, dismissing the dropdown. You will also need to have an option to finalize the selection.
But I reccommend you to look another approach for this case for a better usability, like a dialog where you can select multiple options: Is there an equivalent widget in flutter to the "select multiple" element in HTML

You can use the following package
https://pub.dev/packages/multiselect
it has a dropdown based implementation instead of Dialog to show options.
PS: I needed this feature in a recent project and had to create my own widget. this is my implementation.

Related

tree rebuilds when keyboard shows

I'm using responsive_sizer package for my app..
my whole tree is rebuilt when the keyboard opens with a textfield.
Here the code of the textfield :
class ProfileNameTextField extends StatefulWidget {
const ProfileNameTextField({Key? key}) : super(key: key);
#override
_ProfileNameTextFieldState createState() => _ProfileNameTextFieldState();
}
class _ProfileNameTextFieldState extends State<ProfileNameTextField> {
TextEditingController? _controller;
String _previousName = "";
FocusNode? _focusNode;
final String _forbiddenCharacters = "1234567890&)°(+=/,;.£\$*€<>\_##";
Widget _subText = Container();
#override
void initState() {
// TODO: implement initState
_controller = TextEditingController();
_previousName = CloudUser.instance.username;
_controller!.text = CloudUser.instance.username;
_focusNode = FocusNode();
_focusNode!.addListener(() {
if(!_focusNode!.hasFocus) {
print("Focus on name textfield is lost");
_onSubmitted(_controller!.text);
}
});
super.initState();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
_focusNode!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Widget? _suffix;
switch(Provider.of<LoadingProvider>(context).state) {
case LoadingState.busy:
_suffix = SpinKitRing(
color: Theme
.of(context)
.primaryColor,
lineWidth: 2,
size: Theme.of(context).textTheme.subtitle1!.fontSize!
);
break;
case LoadingState.idle:
_suffix = Container();
break;
}
return CustomTextContainer(
child: InkWell(
onTap: _giveFocus,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children:
[
Text(
"Prénom",
style: Theme.of(context).textTheme.bodyText2!.copyWith(
fontSize: Theme.of(context).textTheme.bodyText2!.fontSize!.sp
)
),
Container(height: Sizer().heightSmallSpace),
Container(height: Theme.of(context).textTheme.bodyText1!.fontSize,
child: Row(children: [
Expanded(
child: TextField(
keyboardType: TextInputType.name,
controller: _controller,
onSubmitted: _onSubmitted,
focusNode: _focusNode,
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
),
style: Theme.of(context).textTheme.bodyText1!.copyWith(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w600,
fontSize: Theme.of(context).textTheme.bodyText1!.fontSize!.sp
),
textAlign: TextAlign.start,
),),
_suffix,
]),
),
Container(height: Sizer().heightSmallSpace),
Row(children: [
Spacer(),
Container(
height: Theme.of(context).textTheme.subtitle1!.fontSize!*1.2,
child: Center(child: _subText)),
]),
Container(height: Sizer().heightSmallSpace),
]
)
)
);
}
_onSubmitted(String username) {
RegExp regExp = RegExp('[' + _forbiddenCharacters + ']');
if(!regExp.hasMatch(username)) {
if(_previousName != username) {
print("name is " + username);
_previousName = username;
setState(() {
_subText = Container();
});
Provider.of<LoadingProvider>(context, listen: false).update('username', username).then((result) {
if(result) {
CloudUser.instance.username = username;
setState(() {
_subText = Text(
"Enregistré",
style: Theme
.of(context)
.textTheme
.subtitle1!
.copyWith(
color: color.success,
fontSize: Theme.of(context).textTheme.subtitle1!.fontSize!.sp
),
);
});
}
else
setState(() {
_subText = Text(
"Erreur serveur",
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Theme.of(context).errorColor,
fontSize: Theme.of(context).textTheme.subtitle1!.fontSize!.sp
),
);
});
});
}
} else {
setState(() {
_subText = Text(
"Caractères interdits",
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Theme.of(context).errorColor,
fontSize: Theme.of(context).textTheme.subtitle1!.fontSize!.sp
),
textAlign: TextAlign.right,
);
});
}
}
_giveFocus() {
_focusNode!.requestFocus();
}
}
Within Sizer(), i have :
double padding = 2.h;
double widgetHeight = 8.h;
double iconButton = 4.h;
double radius = 15;
double lineWidth = 3.h;
double heightSpace = 3.h;
double heightSmallSpace = 0.9.h;
double gridSpacing = 0.3.h;
double widthSpace = 1.25.w;
ProfileNameTextField is included in
class _ProfileControllerState extends State<ProfileController> {
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).backgroundColor,
child: Column(
children: [
ProfileAppBar(
onSetting: _onSetting,
),
Flexible(
child: Container(
padding: EdgeInsets.symmetric(horizontal: Sizer().padding/3),
color: Theme.of(context).scaffoldBackgroundColor,
child: Scrollbar(
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Container(
padding: EdgeInsets.symmetric(horizontal: Sizer().padding*2/3),
child: Column(children:
[
Container(height: Sizer().heightSpace),
SvgPicture.asset(
"assets/icons/phone_kisses.svg",
height: Sizer().widgetHeight*3,
width: Sizer().getCustomWidth(66),
fit: BoxFit.contain,
),
_space(),
ChangeNotifierProvider<LoadingProvider>(
create: (BuildContext context) => LoadingProvider(),
child: ProfileNameTextField(),
),
I have this problem since the import of the responsive_sizer... I do not understand where the problem can come from.
I tried resizetoavoidbottominset but nothing changed.
I found the problem. It's not coming from MediaQuery, but from the Responsive_sizer package.
When I open the keyboard, I actually update the height and width. But this package must necessarily encompass your MaterialApp in the following way:
MaterialApp(
home: ResponsiveSizer(
builder: (context, orientation, screenType) {
return const HomePage();
},
),
);
And that's the problem. Under my homepage, I have a stream to see if the user is logged in, which then leads to the profile page, among other things. I don't want it to reload, I just want the profile to reload.
My solution: I use MediaQuery in a similar way to Responsive_sizer.... instead of using the .h and .w package, I use MediaQuery.of(context).size.height and its counterpart. The same thing for the font size.
I hope this can help those who have the same problem as me on this package,
good evening.

How to evenly space each container of toggle button to fit screen and make selected container be rounded in flutter

I want to create toggle buttons and evenly space each element in the list of toggle buttons and make each selected button rounded like this,
I've tried using boxconstraints property, width property, margin property and the rest,
But this is what I'm getting, I've tried every other thing but I can't get it, this is what I'm getting
This is the code I'm using
import 'package:flutter/material.dart';
class TestingScreen extends StatefulWidget {
#override
State<TestingScreen> createState() => _TestingScreenState();
}
class _TestingScreenState extends State<TestingScreen> {
List<bool> _isSelected = [true, false, false, false];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 100),
child: Row(
children: [
Text(
'Time',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
ToggleButtons(
color: Color(0xff001666),
fillColor: Color(0xff001666),
selectedColor: Colors.white,
children: [
ToggleButton(name: '1D'),
ToggleButton(name: '1W'),
ToggleButton(name: '1M'),
ToggleButton(name: '1Y'),
],
isSelected: _isSelected,
onPressed: (int newIndex) {
setState(() {
for (int i = 0; i < _isSelected.length; i++) {
if (i == newIndex) {
_isSelected[i] = true;
} else {
_isSelected[i] = false;
}
print(_isSelected);
}
});
},
)
],
),
),
);
}
}
class ToggleButton extends StatelessWidget {
final String name;
const ToggleButton({Key? key, required this.name}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.1,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)),
padding: EdgeInsets.symmetric(vertical: 4),
alignment: Alignment.center,
child: Text(
name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
);
}
}
You can add renderBorder: false, property to remove the ash colored border and borderRadius: BorderRadius.circular(15), to make the round circled border in the outside and make the shape you can use constraints: const BoxConstraints.expand(height: 25,width: 34), to get the the exact size of the height and the width.enter image description here
but to get the exact result you have to use Inkwell() or ElvatedButton() or IconButton() bcz there isnt any property to use the borderRadius: BorderRadius.circular(15), for the each of the icons in the buttons as showed in the picture.
Hope it will work for you.

Flutter/Dart - Dynamically controlling CheckboxListTile inside Listview.Builder

I am trying to create a page that lists a number of questions. For each question I will have different answers. At the minute, whenever I select an answer, the same option on the other questions are selected at the same time. How can I avoid this and make each question its own entity? Here is my code:
class QuestionScreen extends StatefulWidget {
#override
_QuestionScreenState createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
List<bool> _isChecked;
final Map<String, Map> questions = {"milk comes from what animal":
{"horse": false, "monkey": false, "frog": false, "cow": true},
"what colour is the sea?":
{"red": false, "green": false, "blue": true, "yellow": false}};
#override
void initState() {
super.initState();
_isChecked = List<bool>.filled(questions.values.length, false);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text("questions page"),
),
Expanded(
child: new ListView.builder(
itemCount: questions.keys.length,
itemBuilder: (BuildContext ctxt, int questionTitleIndex) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Container(
height: MediaQuery.of(context).size.height * 0.45,
decoration: BoxDecoration(
color: OurTheme().ourCanvasColor,
borderRadius: BorderRadius.circular(25),
),
child: Column(
children: [
Text(
questions.keys.toList()[questionTitleIndex],
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w800),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
shrinkWrap: true,
itemCount: questions.values
.toList()[questionTitleIndex]
.keys
.length,
itemBuilder:
(BuildContext ctxt, int questionAnswersIndex) {
return Container(
decoration: BoxDecoration(border: Border.all()),
child: CheckboxListTile(
title: Text(
"${questionAnswersIndex + 1}. ${questions.values.toList()[questionTitleIndex].keys.toList()[questionAnswersIndex]}",
style: TextStyle(
color: Colors.white, fontSize: 16),
),
value: _isChecked[questionAnswersIndex],
controlAffinity:
ListTileControlAffinity.platform,
onChanged: (bool value) {
setState(
() {
_isChecked[questionAnswersIndex] =
value;
},
);
},
activeColor: OurTheme().ourCanvasColor,
checkColor: Colors.white,
),
);
},
),
)
],
),
),
);
},
),
)
],
),
);
}
}
I see a few problems here.
First since your need to maintain the answer for each question in your _isChecked. It would make more sense to make it a Map<String, String> instead of a List<bool>.
Inside it, the key will be the question title and the value will be the selected option title.
So, inside your initState, you will initiate it liket this.
Map<String, String> _isChecked = {}; // Initializing with empty map
#override
void initState() {
super.initState();
widget.questions.keys.forEach((key) {
// For each question we first set the answer as "". means nothing selected.
_isChecked[key] = "";
// We then loop over the options of that question to see if any option was already true and set it as the initial answer.
for (MapEntry entry in widget.questions[key]!.entries) {
if (entry.value) _isChecked[key] = entry.key;
}
});
}
After this, you just have to change the places in your code where you were using the _isChecked variable.
Here is the link to the full working code. Just replace all your code with the code in the link.
Result.

Deleting specific item out of ListView with Bloc

I have a page that consists of a ListView, which contains TextFormFields. The user can add or remove items from that ListView.
I use the bloc pattern, and bind the number of Items and their content inside the ListView to a list saved in the bloc state. When I want to remove the items, I remove the corresponding text from this list and yield the new state. However, this will always remove the last item, instead of the item that's supposed to be removed. While debugging, I can clearly see that the Item I want removed is in fact removed from the state's list. Still, the ListView removes the last item instead.
I've read that using keys solves this problem and it does. However, if I use keys there is a new problem.
Now, the TextFormField will go out of focus every time a character is written. I guess this is to do with the fact that the ListView is redrawing its items everytime a character is typed, and somehow having a key makes the focus behave differently.
Any ideas how to solve this?
The page code (The ListView is at the bottom):
class GiveBeneftis extends StatelessWidget {
#override
Widget build(BuildContext context) {
var bloc = BlocProvider.of<CreateChallengeBloc>(context);
return BlocBuilder<CreateChallengeBloc, CreateChallengeState>(
builder: (context, state) {
return CreatePageTemplate(
progress: state.progressOfCreation,
buttonBar: NavigationButtons(
onPressPrevious: () {
bloc.add(ProgressOfCreationChanged(nav_direction: -1));
Navigator.of(context).pop();
},
onPressNext: () {
bloc.add(ProgressOfCreationChanged(nav_direction: 1));
Navigator.of(context).pushNamed("create_challenge/add_pictures");
},
previous: 'Details',
next: 'Picture',
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'List the benefits of you Challenge',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
SizedBox(height: 30),
Text(
'Optionally: Make a list of physical and mental benefits the participants can expect. ',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey,
fontSize: 14,
fontWeight: FontWeight.w400),
),
SizedBox(height: 50),
Container(
margin: EdgeInsets.all(8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.yellow[600]),
child: FlatButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () => bloc.add(ChallengeBenefitAdded()),
child: Text('Add a benefit',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold)),
),
),
Expanded(
child: new ListView.builder(
itemCount: state.benefits.length,
itemBuilder: (BuildContext context, int i) {
final item = state.benefits[i];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 25),
child: TextFieldTile(
//key: UniqueKey(),
labelText: 'Benefit ${i + 1}',
validator: null,
initialText: state.benefits[i],
onTextChanged: (value) => bloc.add(
ChallengeBenefitChanged(
number: i, text: value)),
onCancelIconClicked: () {
bloc.add(ChallengeBenefitRemoved(number: i));
},
));
})),
],
),
);
});
}
}
The Code of the TextfieldTile:
class TextFieldTile extends StatelessWidget {
final Function onTextChanged;
final Function onCancelIconClicked;
final Function validator;
final String labelText;
final String initialText;
const TextFieldTile(
{Key key,
this.onTextChanged,
this.onCancelIconClicked,
this.labelText,
this.initialText,
this.validator})
: super(key: key);
#override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: initialText,
validator: validator,
onChanged: onTextChanged,
maxLines: null,
decoration: InputDecoration(
labelText: labelText,
)),
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(Icons.cancel), onPressed: onCancelIconClicked),
),
]);
}
}
The relevant portion of the Bloc:
if (event is ChallengeBenefitAdded) {
var newBenefitsList = List<String>.from(state.benefits);
newBenefitsList.add("");
yield state.copyWith(benefits: newBenefitsList);
}
else if (event is ChallengeBenefitChanged) {
var newBenefitsList = List<String>.from(state.benefits);
newBenefitsList[event.number] = event.text;
yield state.copyWith(benefits: newBenefitsList);
}
else if (event is ChallengeBenefitRemoved) {
var newBenefitsList = List<String>.from(state.benefits);
newBenefitsList.removeAt(event.number);
yield state.copyWith(benefits: newBenefitsList);
}
I can think of two things you can do here.
Create a different bloc for processing the changes in the text field, that will avoid having to actually update the state of the entire list if no needed.
Have a conditional to avoid rebuilding the list when your bloc change to a state that is relevant only to the keyboard actions.
Example:
BlocBuilder<CreateChallengeBloc, CreateChallengeState>(
buildWhen: (previousState, currentState) {
return (currentState is YourNonKeyboardStates);
}
...
);

I want to change the color of CustomListTile which is child of ListView when onTap is clicked, and setting other children color into default one?

In a Drawer, in listview want to change the color of CustomListTile when the onTap is clicked and setting color of all other children to default?
class CustomListTile extends StatelessWidget {
final Color itemContainerColor;
const CustomListTile({
//deafult color is Colors.white
this.itemContainerColor= Colors.white,
});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: (){},
child: Container(
margin: EdgeInsets.symmetric(vertical: 4),
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 16),
width: 150,
decoration: BoxDecoration(
color: itemContainerColor,
)
child:
Text(
"ListTile 1",
style: TextStyle(
fontSize: 20,
color: Colors.green,
fontWeight: FontWeight.w600),
),
),
));
}
}
Try this.
class ChangeListViewBGColor extends StatefulWidget {
_ChangeListViewBGColorState createState() => _ChangeListViewBGColorState();
}
class _ChangeListViewBGColorState extends State<ChangeListViewBGColor> {
final List<String> _listViewData = [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
];
int _selectedIndex = 0;
_onSelected(int index) {
setState(() => _selectedIndex = index);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('BG change'),
),
body: ListView.builder(
itemCount: _listViewData.length,
itemBuilder: (context, index) => Container(
color: _selectedIndex != null && _selectedIndex == index
? Colors.red
: Colors.white,
child: ListTile(
title: CustomListTile(_listViewData[index]),
onTap: () => _onSelected(index),
),
),
),
);
}
}
class CustomListTile extends StatelessWidget {
var titleName;
CustomListTile(this.titleName);
#override
Widget build(BuildContext context) {
return InkWell(
child: Container(
child: Text(
this.titleName,
style: TextStyle(
fontSize: 20, color: Colors.green, fontWeight: FontWeight.w600),
),
margin: EdgeInsets.symmetric(vertical: 4),
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 16),
width: 150,
)
);
}
}
Aakash Just use a boolean on tab like colorChange = true when button clicked and other hands in child widget of container...
colorChange ?
Text(
"ListTile 1",
style: TextStyle(
fontSize: 20,
color: Colors.red, // which color you need to use
fontWeight: FontWeight.w600),
): Text(
"ListTile 2",
style: TextStyle(
fontSize: 20,
color: Colors.green,
fontWeight: FontWeight.w600),
)
Note: One can go according to #Amit Prajapati 's solution/logic, but if
your use-case is going to get complex over time then I would recommend
going as per the below solution.
Whenever you need to change the property of a specific element from a list of elements, use a list of values having the same datatype as that of the value accepted by that property having the same length as that of the number of elements in your main list.
In your case you need to change the color of a specific ListTile whenever the user clicks on it. So declare a List of Colors.
List<Color> tileColors;
Now in initState() of your main widget initialize the List with its default values (depending on the length of our main widget).
for(int i=0;i<items.length;i++) tileColors.add(defaultColor);
Now while using a builder function set each item(ListTile) of your list with tileColors[index],
(I'll be using ListView.builder)
ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
color: tileColors[index],
child: ListTile([...]),
);
}
)
Now just setState() the value of the property(color) of that ListTile whenever the user taps on it.
ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
color: tileColors[index],
child: ListTile(
onTap: (){
setState((){
for(int i=0;i<item.length;i++) tileColors[i] = defaultColor;
tileColors[index] = selectedColor; // selectedColor is the color which you wish to change to.
// Note: You can add your own conditions over. e.g. If you want the first tile to be of a specific color and the rest should be different color while the user taps.
});
}
),
);
}
)
Tip: You can use AnimatedContainer instead of Container widget to create a
smooth transition when the user taps. Set the duration parameter to Duration(milliseconds: 300).