Reputation: 3076
I have a ListView with custom statefull widgets as items. These items has an onTap event that switch the icon from check_box_outline_blank to check_box, and the other way around.
However when I select an item in the list and scroll down and back up again the item has reset the icon from check_box to check_box_outline_blank.
How can I prevent this from happening?
new Expanded(
child: ListView.builder(
itemCount: _list.length,
itemBuilder: (BuildContext context, int index) {
return listTile(_pveList[index], index)
},
)
)
//Adds extra space(height:100) after the last tile
StatefulWidget listTile(String text, int index){
return (index < _pveList.length - 1)?new SelectableListItem(text: text,
onTap: (){
_handleOnTap(text);
},
): new Column(
children: <Widget>[
new SelectableListItem(text: text,
onTap: (){
_handleOnTap(text);
},
),
SizedBox(
height: 100,
)
],
);
}
The tile widget(SelectableListItem):
import 'package:flutter/material.dart';
class SelectableListItem extends StatefulWidget {
SelectableListItem({Key key, this.text, this.onTap})
: super(key: key);
final String text;
final GestureTapCallback onTap;
@override
_SelectableListItemState createState() => _SelectableListItemState();
}
class _SelectableListItemState extends State<SelectableListItem> {
bool isSelected = false;
Icon _checked = new Icon(Icons.check_box_outline_blank);
handleOnTap() {
isSelected = !isSelected;
if (isSelected) {
setState(() {
_checked = new Icon(Icons.check_box);
});
} else {
setState(() {
_checked = new Icon(Icons.check_box_outline_blank);
});
}
return widget.onTap();
}
@override
Widget build(context) {
// Pass the text down to another widget
return new GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => handleOnTap(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Padding(
padding: EdgeInsets.all(22),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(widget.text, style: TextStyle(fontSize: 18)),
_checked
],
),
),
new Container(
width: double.infinity,
height: 1,
color: Color(0x1a222222),
)
],
),
);
}
}
Upvotes: 1
Views: 1474
Reputation: 10334
I believe the OP wants to achieve a standard Checkbox with a label text aside.
CheckboxListTile
widget provides such functionality.
Usage example:
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
CheckboxListTile(
onChanged: (val) {
setState(() {
selected = !selected;
});
},
value: selected,
title: Text("Test"),
)
]),
);
}
If you want to have the checkbox located on the left side of the screen - set controlAffinity
property accordingly:
CheckboxListTile(
// ...,
controlAffinity: ListTileControlAffinity.leading,
)
You should never use functions to build the widget tree.
See this answer for more details.
I'd recommend to use ValueNotifier
, declared in SelectableListItem
class.
It allows you to change value of a variable in StatefulWidget
without the usual warnings for non-final variables.
The value inside of ValueNotifier
gets preserved too, just like any other variables that are declared in StatefulWidget
class.
e.g.
class SelectableListItem extends StatefulWidget {
// ...
final ValueNotifier<bool> isSelected = ValueNotifier<bool>(false);
// ...
}
class _SelectableListItemState extends State<SelectableListItem> {
handleOnTap() {
setState(() {
widget.isSelected.value = !widget.isSelected.value;
});
return widget.onTap();
}
// ... build() { ...
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(widget.text, style: TextStyle(fontSize: 18)),
Icon(widget.isSelected.value ? Icons.check_box : Icons.check_box_outline_blank),
]
),
// ... } ...
}
ValueNotifier<bool>
also eliminates the need to store the icon widget in a separate variable. Instead, you can use it in inline boolean condition, just like it's shown in the code.
PS: I did not test the code, however general instructions are valid. Let me know if this helped.
Upvotes: 1