I have US states displayed on the screen. They are displayed using a ListView. I need to make it so that when you click on one of the states, a checkmark appears. Now in the trailing I added an icon, but when you click on one state, a checkmark appears on all. How can this be implemented?
class _AddStatePageState extends State<AddStatePage> {
static const List<String> _usaStates = [
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
...
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarWithSearch(
appBarTitle: 'Add State',
),
body: Padding(
padding: const EdgeInsets.only(top: 24),
child: ListView.separated(
itemCount: _usaStates.length,
itemBuilder: (context, index) {
return ListTile(
trailing: Image.asset(
Assets.assetsCheckmark,
width: 13,
height: 10,
),
title: Text(
_usaStates[index],
),
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
),
);
}
}
Something along these lines:
class _AddStatePageState extends State<AddStatePage> {
static const List<String> _usaStates = [
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
...
];
static const List<bool> _usaStatesSelected = [false, false, true, ...];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarWithSearch(
appBarTitle: 'Add State',
),
body: Padding(
padding: const EdgeInsets.only(top: 24),
child: ListView.separated(
itemCount: _usaStates.length,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
setState(() {
for(var i = 0; i < _usaStatesSelected.length; i++) {
_usaStatesSelected[i] = false;
}
_usaStatesSelected[index] = true;
});
},
trailing:
_usaStatesSelected[index] == false
? SizedBox.shrink()
: Image.asset(
Assets.assetsCheckmark,
width: 13,
height: 10,
),
title: Text(
_usaStates[index],
),
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
),
);
}
}
ListTile provide onTap method, you can use it. To show selected item, create a variable that will holds the selected index on state class.
int? _selectedIndex;
and ListTile will be
return ListTile(
onTap: () {
_selectedIndex=index;
setState(() {});
},
trailing:
_selectedIndex==index ? Icon(Icons.check) : null,
Replace Icon(Icons.check) with your image.
Related
I have a list of ExpansionTile with a list of ListTile in a Drawer. What I want to achieve is, when I press an ExpansionTile, the another ExpansionTile must be collapsed. I had been stuck with this problem for two days and could not find an answer. Can anybody know how to collapse the ExpansionTile programmatically?
Note:
I don't want to mess up the animation of the widget.
Here is my code,
ListView.builder(
itemCount: userList.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, findex) {
return ExpansionTile(
key: Key(findex.toString()),
title: Text(userList[findex].parentdata[0].title,
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
),
onExpansionChanged: (value) {
},
children: [
ListView.builder(
itemCount: userList[findex].document.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, sindex) {
return ListTile(
title: Text(
userList[findex].document[sindex].title,
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
),
onTap: () {
print(
userList[findex].document[sindex].title);
},
);
},
),
],
);
},
),
Try below code
declare one int variable
int selectedTile = -1;
Your widget
ListView.builder(
key: Key(selectedTile.toString()),
itemCount: 5,
itemBuilder: (context, index) {
return ExpansionTile(
key: Key(index.toString()),
initiallyExpanded: index == selectedTile,
title: Text('ExpansionTile $index'),
subtitle: Text('Trailing expansion arrow icon'),
children: [
ListTile(
title: Text('This is tile number $index'),
),
],
onExpansionChanged: ((newState) {
if (newState)
setState(() {
selectedTile = index;
});
else
setState(() {
selectedTile = -1;
});
}),
);
},
);
Use ExpansionPanel widget.
You need to create a variable and maintain the expansion state of expansion panel index.
expansionCallback: (int index, bool isExpanded) {
setState(() {
// when any of expansionPanel is Tapped
// set all expansion to false
for(int i = 0; i<_data.length; i++){
_data[i].isExpanded = false;
}
// then set the tapped index to its state
_data[index].isExpanded = !isExpanded;
});
},
Here is an live demo for expansion panel
Try this:
Create a variable: int selected = -1;
And listview:
ListView.builder(
itemCount: 10,
shrinkWrap: true,
itemBuilder: (BuildContext context, findex) {
return ExpansionTile(
initiallyExpanded: findex == selected,
key: Key(selected.toString()),
title: Text(userList[findex].parentdata[0].title,
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
),
onExpansionChanged: (newState) {
setState(() {
selected = findex;
});
},
children: [
ListView.builder(
itemCount: 10,
shrinkWrap: true,
itemBuilder: (BuildContext context, sindex) {
return ListTile(
title: Text(
userList[findex].document[sindex].title,
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
),
onTap: () {
print(userList[findex].document[sindex].title);
},
);
},
),
],
);
},
),
Make sure ExpansionTile be in stateful widget
ListView.builder(
itemCount: 5,
shrinkWrap: true,
itemBuilder: (BuildContext context, index) {
return CustomExpansionTile(index: index);
},
),
// Expansion Tile Widget
class CustomExpansionTile extends StatefulWidget {
final int index;
const CustomExpansionTile({Key? key, required this.index}) : super(key: key);
#override
State<CustomExpansionTile> createState() => _CustomExpansionTileState();
}
class _CustomExpansionTileState extends State<CustomExpansionTile> {
int selectedIndexExpansionTile = -1;
#override
Widget build(BuildContext context) {
return ExpansionTile(
initiallyExpanded: widget.index == selectedIndexExpansionTile,
key: Key(selectedIndexExpansionTile.toString()),
title: Text(
widget.index.toString(),
),
onExpansionChanged: (newState) {
if (newState) {
selectedIndexExpansionTile = widget.index;
} else {
selectedIndexExpansionTile = -1;
}
setState(() {});
},
children: [Text(widget.index.toString())]);
}
}
Hello I have 2 list and I want to use these in ListViewBuilder.
List's:
List times = ['08:30', '09:00', '09:30', '10:00', '13:00'];
List obj = [true,false,true];
I tried this:
ListView.builder(
controller: scrollController,
shrinkWrap: true,
itemCount: times.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
setState(() {
selected = index;
debugPrint(tarih[index]);
});
},
child: Container(
color: obj[index] ? Colors.yellow : Colors.red,
height: 30,
width: 12,
child: Text(times[index]),
),
),
);
},
),
Here is the error:
I know what cause's this error. Because of obj.length does not match with times.length
But I still want to create the other Containers.
How do I solve this?
Many ways you can avoid this here min(int,int) method used lowest integer find
obj[min(index,obj.length-1)] ? Colors.yellow : Colors.red,
widget may like this
ListView.builder(
// controller: scrollController,
shrinkWrap: true,
itemCount: times.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
setState(() {
selected = index;
// debugPrint(tarih[index]);
});
},
child: Container(
color: obj[min(index,obj.length-1)] ? Colors.yellow : Colors.red,
height: 30,
width: 12,
child: Text(times[index]),
),
),
);
},
)
You try to achieve dartpad live
class _MyAppState extends State<MyApp> {
int selected = 0;
#override
void initState() {
super.initState();
}
List times = ['08:30', '09:00', '09:30', '10:00', '13:00'];
List obj = [];
#override
Widget build(BuildContext context) {
var column = Column(
children: [
Container(
height: 200,
child: Row(
children: [
Expanded(
child: Image.network(
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Salto_del_Angel-Canaima-Venezuela08.JPG/1200px-Salto_del_Angel-Canaima-Venezuela08.JPG",
// fit: BoxFit.cover,
fit: BoxFit.fitWidth,
),
),
],
),
)
],
);
obj = List.generate(times.length, (index) => false);
var children2 = [
ListView.builder(
// controller: scrollController,
shrinkWrap: true,
itemCount: times.length,
itemBuilder: (BuildContext context, int index) {
if (selected == index)
obj[index] = true;
else
obj[index] = false;
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: selected != index
? () {
setState(() {
selected = index;
print(selected);
// debugPrint(tarih[index]);
});
}
: null,
child: Container(
color: obj[index]
? Colors.yellow
: Colors.red,
height: 30,
width: 12,
child: Text(times[index]),
),
),
);
},
),
DropdownButton(
items: [
DropdownMenuItem(
child: Text("1"),
value: "1",
onTap: () {},
)
],
onChanged: (values) {
// _dropdown1=values;
})
];
return MaterialApp(
// theme: theme(),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children2,
)),
);
}
}
Well there might be other ways to do so, what I did was, just to copy the obj list items as many as there are items in times list in order. And if the number is not equal just add remaining number at last.
List times = [
'08:30',
'09:00',
'09:30',
'10:00',
'13:00',
'13:00',
'13:00',
'13:00',
'13:00',
];
List obj = [true, false, true];
#override
Widget build(BuildContext context) {
// Remaining length of times list after we copy
int remaining = times.length % obj.length;
//Exact Number of times to copy obj
int exactNumber = (times.length - remaining) ~/ obj.length;
List shallowList = [];
// Using for loop copy the list as many as exactNumber
for (int i = 0; i < exactNumber; i++) {
shallowList += obj;
}
// Add add remaining subList
// Then we have obj list with same length as times
List finalShallowList = [...shallowList, ...obj.sublist(0, remaining)];
// Create Separate Index for obj that we can reset
return Scaffold(
body: Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: times.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {},
child: Container(
// Loop over finalShallowList instead of obj
color: finalShallowList[index] ? Colors.yellow : Colors.red,
height: 30,
width: 12,
child: Text(times[index]),
),
),
);
},
),
),
);
Here is my code below. I have tried using _navigation.indexOf and .indexWhere but both want to return integers and won't let me convert them into num dataTypes. I am hoping to generate at least 5 containers that decrease in size (e.g. 450 - 50*index) that can function as navigation buttons. If this cant work with a listview.builder, would a for loop be an option instead?
import 'package:flutter/material.dart';
class Navigation {
final String title;
Navigation(this.title);
}
class HomeScreen extends StatelessWidget {
static const String id = '/';
List<Navigation> _navigation = [
Navigation('Title1'),
Navigation('Title2'),
Navigation('Title3'),
Navigation('Title4'),
Navigation('Title5'),
];
int findIndex(List<Navigation> _navigation, String buttonName) {
final getIndex =
_navigation.indexWhere((element) => element.title == buttonName);
return getIndex;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: ListView.builder(
itemCount: _navigation.length,
itemBuilder: (BuildContext nav, int index) {
return TextButton(
onPressed: () {
Navigator.pushNamed(context, _navigation[index].title);
},
child: Container(
height: 50,
width: 450,
child: Text(
'${_navigation[index].title}',
textAlign: TextAlign.end,
)));
}));
}
}
i can think of two method.
First Use margin in Container widget like this.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: ListView.builder(
itemCount: _navigation.length,
itemBuilder: (BuildContext nav, index) {
return Container(
margin: EdgeInsets.only(
left: (20.0 * index),
),
alignment: AlignmentDirectional.centerStart,
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, _navigation[index].title);
},
child: Text(
_navigation[index].title,
textAlign: TextAlign.center,
),
),
);
},
),
);
}
Second use Padding widget like this.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: ListView.builder(
itemCount: _navigation.length,
itemBuilder: (BuildContext nav, index) {
return Padding(
padding: EdgeInsets.only(
left: (50.0 * index),
),
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, _navigation[index].title);
},
child: Text(
_navigation[index].title,
textAlign: TextAlign.center,
),
),
);
},
),
);
}
I have created a string List and applied a checkbox and when the checkbox clicked, the string list will be shown on the next screen but I am getting a range error. please help.
var _suggestions = <String>['this is me1','this is me2','this is me3' ];
final _saved = <String>['this is me1','this is me2','this is me3' ];
final _biggerFont = TextStyle(fontSize: 18.0);
this is the string that I have defined.
void _pushSaved(){
Navigator.of(context).push(
MaterialPageRoute<void>(
// NEW lines from here...
builder: (BuildContext context) {
final tiles = _saved.map(
(String pair) {
return ListTile(
title: Text(
pair,
style: _biggerFont,
),
);
},
);
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved Suggestions'),
),
body: ListView(children: divided),
);
}, // ...to here.
),
);
}
this is some page route
Widget _buildSuggestions() {
return ListView.builder(
padding: EdgeInsets.all(16.0),
itemBuilder: /*1*/ (context, i) {
if (i.isOdd) return Divider(); /*2*/
final index = i ~/ 2; /*3*/
_suggestions = <String>['this is me1','this is me2','this is me3'];
return _buildRow(_suggestions[index]);
});
}
There is no problem in showing selected checkbox data into the next screen but I don't know why the range error is showing.
Widget _buildRow(String pair) {
final alreadySaved = _saved.contains(pair);
return Container(
decoration: new BoxDecoration (
color: HexColor('#F2FFFF'),
border: Border.all(color: HexColor('#09B9B6')),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: ListTile(
title: Text(
pair,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.check_box : Icons.check_box_outline_blank_outlined,
color: alreadySaved ? HexColor('#09B9B6') :null,
),
onTap: (){
setState(() {
if (alreadySaved){
_saved.remove(pair);
}
else{
_saved.add(pair);
}
});
},
),
);
}
this is the build row function
We can also use ListView.separated
ListView.separated(
padding: EdgeInsets.all(16.0),
itemBuilder: (context, index) {
return _buildRow(_suggestions[index]);
},
separatorBuilder: (_, __) => Divider(),
itemCount: _suggestions.length);
I know this question has been asked before, and here's the link to that question:
How do i change the color and text of Container at onTap event in FLUTTER
However in my case I have around 50 containers in a row, so using the provided solution would be extremely lengthy.
Is there any shortcut using UniqueKeys()??
Try this:
class _TestState extends State<Test> {
int selected = 0;
List<Widget> _containerList() {
return List.generate(50, (index) {
return GestureDetector(
onTap: () {
setState(() {
selected = index;
});
},
child: Container(
height: 200,
width: 200,
color: selected == index ? Colors.blue : Colors.transparent,
child: Text("$index"),
),
);
}).toList();
}
#override
Widget build(BuildContext context) {
return Column(
children: _containerList(),
);
}
}
OR
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
selected = index;
});
},
child: Container(
height: 200,
width: 200,
color: selected == index ? Colors.blue : Colors.transparent,
child: Text("$index"),
),
);
}
)
OR
Wrap(children: _containerList(),)
You can do something like this..
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: () async {
//write your code here to update the list*********
},
child: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
setState(() { _selectedIndex = index; });
},
child: Text(
'Line $index',
style: TextStyle(
color: _selectedIndex == index ? Colors.red : Colors.black,
)
),
);
}
)
),
);
}
}
List<String> _selectedIndexs = [];ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
final _isSelected = _selectedIndexs.contains(index);
return GestureDetector(
onTap: () {
setState(() {
if (_isSelected) {
_selectedIndexs.remove(index);
} else {
_selectedIndexs.add(index);
}
print(_selectedIndexs);
});
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 25, vertical: 10),
decoration: BoxDecoration(
color: _isSelected
? ContainerColors.selectedBgColor
: priorityChipBg,
borderRadius: BorderRadii.radius8px,
border: Border.all(
color: _isSelected
? BorderColor.bordersecondaryBlue
: chipBorderColor)),
child: Text(
requestPriority[index],
style: _isSelected
? AppFonts.mediumTextBB.copyWith(
color: TextColors.themeColor,
fontSize: 14,
fontWeight: FontWeight.w900)
: chipTextStyle,
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
width: 20,
);
},
itemCount: 3,
),
),