Handling children taps - flutter

I'm beginner on Flutter and I'm trying to create a custom Widget called IconSelect. It should render a list of icons with a legend and the user will choose only one option. When the user taps an icon, it should change the background color of the selected icon and deselect all others.
My first aproach was to create an IconSelect class as a Stateful widget, and another widget called IconSelectItem as Stateless. And the IconSelect would have a children property, containing instances of IconSelectItem.
How can I handle the children taps to change the IconSelect state? Any ideas of others aproaches?
My code:
class IconSelect extends StatefulWidget {
final List<IconSelectItem> children;
final ValueChanged<int> onSaved;
IconSelect({
this.children,
this.onSaved
});
#override
State<StatefulWidget> createState() => new IconSelectState();
}
class IconSelectState extends State<IconSelect> {
int _selectedValue;
_handleTap(int value) {
setState(() {
_selectedValue = value;
});
widget.onSaved(_selectedValue);
}
#override
Widget build(BuildContext context) {
return new Row(
children: widget.children,
);
}
#override
void initState() {
super.initState();
// I tried the code below without success
widget.children.forEach((IconSelectItem item) {
item.onTap = _handleTap(item);
});
}
}
class IconSelectItem extends StatelessWidget {
final Icon icon;
final String legend;
final int value;
VoidCallback onTap;
final bool _selected = false;
IconSelectItem({
Key key,
this.icon,
this.legend,
this.value,
}) : super(key: key);
_handleTap() {
onTap();
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () => _handleTap(),
child: new Column(
children: <Widget>[
new CircleAvatar(
radius: 30.0,
child: icon,
backgroundColor: _selected ? Colors.blue : Colors.white,
),
new Center(
child: new Text(legend),
)
],
),
);
}
}

call setState on IconSelectItem's ancestor:
class YourPageState extends State<YourPage> {
int _selectedValue;
#override
Widget build(BuildContext context) {
return new Row(
children: widget.items.map((Item item) {
return new GestureDetector(
onTap: () {
// this class is a ancestor of IconSelectItem.
// setState will rebuild children.
setState(() {
_selectedValue = value;
});
},
child: new IconSelectItem(
icon: item.icon,
legend: item.legend,
value: item.value,
// every time _selectedValue changes,
// IconSelectItem is rebuild by setState.
selected: item.value == _selectedValue,
),
);
}).toList(),
);
}
}
class IconSelectItem extends StatelessWidget {
final Icon icon;
final String legend;
final int value;
final bool selected;
IconSelectItem({
Key key,
this.icon,
this.legend,
this.value,
this.selected = false,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new CircleAvatar(
radius: 30.0,
child: icon,
backgroundColor: selected ? Colors.blue : Colors.white,
),
new Center(
child: new Text(legend),
),
],
);
}
}

Related

Can we change a widgets variable with InkWell onTap function?

I have a custom written stateful widget that wrapped with InkWell and I want to change the widgets variable when onTap function gets activated. Is there any way to achieve that?
Here is my custom written widget
import 'package:flutter/material.dart';
class DrawerListTile extends StatefulWidget {
final tileIcon;
final tileText;
bool isSelected = false;
DrawerListTile({this.tileIcon, this.tileText});
#override
State<DrawerListTile> createState() => _DrawerListTileState();
}
class _DrawerListTileState extends State<DrawerListTile> {
#override
Widget build(BuildContext context) {
return ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
selected: widget.isSelected,
selectedTileColor: Colors.black12,
selectedColor: Colors.black54,
leading: Icon(widget.tileIcon),
title: Text(widget.tileText),
);
}
}
And here is my InkWell widget
InkWell(
onTap: () => setState(() {
//Here is the part that I want to change the DrawerListTile's isSelected value
}),
child: DrawerListTile(
tileText: "Some Text", tileIcon: Icons.credit_card_rounded),
),
I know that I can write the onTap function inside the DrawerListTile but it is not useful in my situation so is there any way to achieve what I want?
You can do something like the below solution ... you can use your isSelected variable for this purpose.
The parent view:
class MainView extends StatefulWidget {
const MainView({Key? key}) : super(key: key);
#override
State<MainView> createState() => _MainViewState();
}
class _MainViewState extends State<MainView> {
String text = DateTime.now().toString();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App'),
),
body: InkWell(
child: Center(child: TargetWidget(text: text)),
onTap: () {
setState(() {
text = DateTime.now().toString();
});
},
),
);
}
}
The child view:
class TargetWidget extends StatefulWidget {
String text;
TargetWidget({Key? key, required this.text}) : super(key: key);
#override
State<TargetWidget> createState() => _TargetWidgetState();
}
class _TargetWidgetState extends State<TargetWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.text),
);
}
}
You should pass your variable as a parameter to your DrawerListTile(),You can create a model class that will hold all the variables you need and pass them to the widget. Thus, whenever you call the setState function, new parameters are sent to the widget and the widget is updated.
Ex:
InkWell(
onTap: () => setState(() {
//Here is the part that I want to change the DrawerListTile's isSelected value
yourFirstVariable = something;
yourSecondVariable = something;
}),
child: DrawerListTile(
tileText: "Some Text",
tileIcon: Icons.credit_card_rounded,
drawerVariables: DrawerModel(
demoVar1 = yourFirstVariable,
demoVar2 = yourSecondVariable...
),
),
),
class DrawerModel {
final var demoVar1;
final var demoVar2;
DrawerModel ({required this.demoVar1, required this.demoVar1,});
}
class DrawerListTile extends StatefulWidget {
final tileIcon;
final tileText;
final DrawerModel drawerVariables;
bool isSelected = false;
DrawerListTile({this.tileIcon, this.tileText, this.drawerVariables});
#override
State<DrawerListTile> createState() => _DrawerListTileState();
}
class _DrawerListTileState extends State<DrawerListTile> {
#override
Widget build(BuildContext context) {
return ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
selected: widget.isSelected,
selectedTileColor: Colors.black12,
selectedColor: Colors.black54,
leading: Icon(widget.tileIcon),
title: Text(widget.tileText),
);
}
}

Color state is changing when I am deleting item from list and do setState((){})

I am assigning random color to a ListTile leading property in flutter, but when I am deleting a item using setState, color of all list items are changing, I tried to use ObjectKey() , I want to keep the color state constant
ListTile(
key: ObjectKey(expense),
onTap: () {
setState(() {
_allExpenses.remove(expense);
});
},
leading: CircleAvatar(
radius: 20,
backgroundColor: Color(Random().nextInt(0xffffffff)),
child: FindIcon(expense.type),
),
title: Text("Test"),
)
I am using provider and notifyListeners(); which make rebuild of the UI on every item delete
In your case, the setState will rebuild every time. If you want to work with keys, a new wrapper of ListTile must be created.
But, there is another alternative. In the initState, a list of random colors can be created:
class MyList extends StatefulWidget {
const MyList({ Key key }) : super(key: key);
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
final List<int> _allExpenses = [2, 4, 5, 6, 9, 10, 22];
List<Color> _colors;
#override
void initState(){
super.initState();
_colors = [
for(final _ in _allExpenses)
Color(Random().nextInt(0xffffffff)),
];
}
void removeOne(int index){
setState(() {
_allExpenses.removeAt(index);
_colors.removeAt(index);
});
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
for(int i = 0; i < _allExpenses.length; i++)
ListTile(
//key: ObjectKey(expense),
onTap: () => removeOne(i),
leading: CircleAvatar(
radius: 20,
backgroundColor: _colors[i],
child: Icon(Icons.account_circle),
),
title: Text("Test ${_allExpenses[i]}"),
),
]
);
}
}
That is a good alternative because you can control the needed memory for those random colors. That logic can be extended to ChangeNotifiers with their notifyListeners() using the addListener() in the initState:
#override initState(){
...
myChangeNotifierList.addListener(() => setState((){}));
}
There is another alternative where you delegate the responsibility of handling the random color and keeping the state of each list tile.
class MyListWithKeys extends StatefulWidget {
const MyListWithKeys({ Key key }): super(key: key);
#override
_MyListWithKeysState createState() => _MyListWithKeysState();
}
class _MyListWithKeysState extends State<MyListWithKeys> {
final List<int> _allExpenses = [2, 4, 5, 6, 9, 10, 22];
void removeOne(int expense){
setState(() {
_allExpenses.remove(expense);
});
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
for(final expense in _allExpenses)
MyTile(
key: ObjectKey(expense),
expense: expense,
onTap: () => removeOne(expense),
),
]
);
}
}
class MyTile extends StatefulWidget {
const MyTile({
Key key,
#required this.expense,
#required this.onTap,
}) : assert(expense != null),
assert(onTap != null),
super(key: key);
final int expense;
final VoidCallback onTap;
#override
_MyTileState createState() => _MyTileState();
}
class _MyTileState extends State<MyTile> {
Color _color;
#override
void initState() {
super.initState();
_color = Color(Random().nextInt(0xffffffff));
}
#override
Widget build(BuildContext context) {
return ListTile(
onTap: widget.onTap,
leading: CircleAvatar(
radius: 20,
backgroundColor: _color,
child: Icon(Icons.account_circle),
),
title: Text('Test ${widget.expense}'),
);
}
}
You should call Random() in the initState so that its not called again when you do a setState.
Color bgColor;
#override
void initState(){
super.initState()
bgColor = Color(Random().nextInt(0xffffffff));
}
and your ListTile
ListTile(
key: ObjectKey(expense),
onTap: () {
setState(() {
_allExpenses.remove(expense);
});
},
leading: CircleAvatar(
radius: 20,
backgroundColor: bgColor,
child: FindIcon(expense.type),
),
title: Text("Test"),
)

How to make ToggleButtons with text under icon

I'm having a bit of a hard time with this idea.
The goal is to have a row of Toggle Icons with text that can overflow onto a second line.
The issue I'm having with the ToggleButtons is that I can't seem to place text underneath each icon.
I currently have a Map<String, Icon> where the string is the text I want below the Icon from that Map.
Is there an easy/possible way to do this?
Yea, you can achieve this by using the Column widget.
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.access_alarm),
SizedBox(height: 5.0,),
Text("Text"),
],
);
Please see the following code to put text under icon in a ToggleButton.
import 'package:flutter/material.dart';
final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Map<String, dynamic> map = {
"one": Icons.ac_unit,
"two": Icons.baby_changing_station,
"three": Icons.cached,
"four": Icons.dangerous,
"five": Icons.east,
"six": Icons.face,
};
List<bool> _isSelected = [];
#override
void initState() {
super.initState();
_isSelected = List.filled(map.length, false);
}
#override
Widget build(BuildContext context) {
return Wrap(
children: [
ToggleButtons(
isSelected: _isSelected,
children: [
...map.entries.map((ele) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(ele.value),
Text(ele.key),
],
);
}).toList(),
],
selectedColor: Colors.blueGrey,
onPressed: (value) {
setState(() {
_isSelected = List.filled(map.length, false);
_isSelected[value] = true;
});
},
),
],
);
}
}
I edited up modifying another answers code from another question to use my map
import 'package:flutter/material.dart';
class WrapToggleIconButtons extends StatefulWidget {
const WrapToggleIconButtons({
#required this.symptomIconDataMap,
#required this.isSelected,
#required this.onPressed,
});
final Map<String, IconData> symptomIconDataMap;
final List<bool> isSelected;
final Function onPressed;
#override
_WrapToggleIconButtonsState createState() => _WrapToggleIconButtonsState();
}
class _WrapToggleIconButtonsState extends State<WrapToggleIconButtons> {
int index;
#override
Widget build(BuildContext context) {
final List<String> symptomsList = widget.symptomIconDataMap.keys.toList();
assert(symptomsList.length == widget.isSelected.length);
index = -1;
return Wrap(
children: symptomsList.map((String symptom) {
index++;
return IconToggleButton(
active: widget.isSelected[index],
iconData: widget.symptomIconDataMap[symptom],
text: symptom,
onTap: widget.onPressed,
index: index,
);
}).toList(),
);
}
}
class IconToggleButton extends StatelessWidget {
const IconToggleButton({
#required this.active,
#required this.iconData,
#required this.text,
#required this.onTap,
#required this.index,
this.width,
this.height,
});
final bool active;
final IconData iconData;
final String text;
final Function onTap;
final double width;
final double height;
final int index;
#override
Widget build(BuildContext context) {
return Container(
width: 80.0,
height: height ?? 60.0,
child: Column(
children: [
InkWell(
child: Icon(
iconData,
color: active ? Theme.of(context).accentColor : Theme.of(context).disabledColor,
),
onTap: () => onTap(index),
),
Wrap(
direction: Axis.horizontal,
children: [
Text(
text,
textAlign: TextAlign.center,
),
],
)
],
),
);
}
}
Flutter: Is there a widget to flex toggle buttons
You can also create a custom widget and use it when you need it.
///CustomTextIcon.dart
import 'package:flutter/material.dart';
class MyIconWithText extends StatelessWidget {
final IconData icon;
final String? text;
const MyIconWithText(this.icon, {Key? key,
this.text
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(icon),
const SizedBox(height: 5.0,),
Text(text ?? ""),
],
);
}
}
and use it as follow:
///Used as a widget
MyIconWithText(Icons.disabled_by_default, text: "Description")

How do I extract this switch widget

I have a StatefulWidget with a ListView, the ListView has the bunch of switches with text next to them.
Now i want to extract this into a custom switch widget because i have this more than once.
I don't know how to do this, also I need to know inside my parent widget what state each switch has.
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Use custom dhcp server"),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Switch(
value: _dhcp,
activeColor: Colors.blue,
onChanged: (bool value) {
setState(() {
_dhcp = value;
});
},
),
),
],
),
),
You can create your own stateless widget like this:
class CustomSwitch extends StatelessWidget {
const CustomSwitch({
Key key,
#required this.value,
#required this.onChanged,
}) : super(key: key);
final bool value;
final void Function(bool) onChanged;
#override
Widget build(BuildContext context) {
return Switch(
value: value,
activeColor: Colors.blue,
onChanged: onChanged,
);
}
}
Where you can use it anywhere like this:
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool switchValue = false;
#override
Widget build(BuildContext context) {
return ListView(
children: [
CustomSwitch(
value: switchValue,
onChanged: (newValue) {
setState(() {
switchValue = newValue;
});
},
),
],
);
}
}

Flutter scaffold updated entire page when update on appbar

So I have a scaffold with body is a list view. And I have an appbar that manage its stage. Here my appbar code :
import 'package:flutter/material.dart';
class HgAppBar extends StatefulWidget implements PreferredSizeWidget {
final String title;
final List<Widget> actions;
HgAppBar({this.title, this.actions, Key key}) : super(key: key);
#override
HgAppBarState createState() => HgAppBarState();
#override
Size get preferredSize => new Size.fromHeight(kToolbarHeight);
}
class HgAppBarState extends State<HgAppBar> {
bool _searchOpenned = false;
void openSeach() {
setState(() {
_searchOpenned = true;
});
}
void closeSearch() {
setState(() {
_searchOpenned = true;
});
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return AppBar(
title: _searchOpenned
? TextField(
decoration: InputDecoration(
filled: true,
border: null,
fillColor: Colors.white,
),
autofocus: true,
)
: Text(widget.title ?? 'No title'),
actions: _searchOpenned
? [
IconButton(
icon: Icon(Icons.close),
onPressed: () {
setState(() {
_searchOpenned = false;
});
},
)
]
: widget.actions,
);
}
}
And here my page code:
class PageSales extends StatefulWidget {
final Store<AppState> store;
final String title;
final bool usePop;
PageSales(this.store, {this.title, this.usePop = false});
#override
State<StatefulWidget> createState() => _PageSales();
}
class _PageSales extends State<PageSales> {
final appBarKey = GlobalKey<HgAppBarState>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HgAppBar(
key: appBarKey,
title: Localizations.of(context, AppLoc).text('sales_plus'),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
appBarKey.currentState.openSeach();
},
)
],
),
body: SafeArea(
child: Column(children: <Widget>[
Expanded(
child: FireStoreListView(
snapshot: HgFirestore.instance.productCollection.snapshots(),
itemBuilder: (context, doc) {
return WidgetProductItem(
widget.store, ProductModel.fromDocument(doc));
},
),
),
]),
),
);
}
}
so the problem is when I call the openSearch, my entire scaffold get refresh (I know it because my ListView is flashing). How do I can update my appbar without refreshing entire scaffold?
I tried your code and it seems to be fine. The screen doesn't rebuild, I'm using Flutter 2.2. I suggest adding debugPrint to make sure that the screen does get rebuild, ListView flashing isn't a definite indicator that the entire screen gets rebuild.