how get selected index of multiple ExpansionTile in flutter ?
i need sidebar menu with multiple expansiontile and listtile.
how can i get selected index to change selected color menu with provider or bloc ?
children: [
ExpansionTile(
title: Text('main a'),
children: [
ListTile(
title: Text('a1'),
),
ListTile(
title: Text('a2'),
),
ExpansionTile(
title: Text('a3'),
children: [
ListTile(
title: Text('a31'),
),
ListTile(
title: Text('a32'),
),
ListTile(
title: Text('a32'),
),
],
),
],
),
ExpansionTile(
title: Text('main b'),
children: [
ListTile(
title: Text('b1'),
),
ListTile(
title: Text('b2'),
),
ListTile(
title: Text('b3'),
),
],
),
],
You can use onTap from ListTile, and create state variables to hold selected item. Like here I am using String. Based on your data, creating model class or map might be better choice.
String? aValue;
....
ExpansionTile(
title: Text('main a'),
children: [
ListTile(
title: Text('a1'),
onTap: () {
aValue = "a1";
setState(() {});
},
),
You can use a ListView to contain the ExpansionTile widgets and a ListTile widgets. Then you can use a currentIndex variable to keep track of the index of the currently selected menu item. You can use a Provider or BLoC to manage the currentIndex variable and to notify the widget tree when the value of currentIndex changes.
Here is the full code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => MenuModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ExpansionTile Demo'),
),
body: MenuList(),
);
}
}
class MenuList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final model = Provider.of<MenuModel>(context);
return ListView(
children: <Widget>[
ExpansionTile(
title: Text('Menu 1'),
children: <Widget>[
ListTile(
title: Text('Menu 1.1'),
onTap: () {
model.updateIndex(0);
},
selected: model.currentIndex == 0,
),
ListTile(
title: Text('Menu 1.2'),
onTap: () {
model.updateIndex(1);
},
selected: model.currentIndex == 1,
),
],
),
ExpansionTile(
title: Text('Menu 2'),
children: <Widget>[
ListTile(
title: Text('Menu 2.1'),
onTap: () {
model.updateIndex(2);
},
selected: model.currentIndex == 2,
),
ListTile(
title: Text('Menu 2.2'),
onTap: () {
model.updateIndex(3);
},
selected: model.currentIndex == 3,
),
],
),
],
);
}
}
class MenuModel with ChangeNotifier {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
void updateIndex(int index) {
_currentIndex = index;
notifyListeners();
}
}
This is a hassle to dynamically change the color of the ListTile() which have two different parent widget but with some extra code, you can do the same.
Full Code
// You can also use `Map` but for the sake of simplicity I'm using two separate `List`.
final List<String> _parentlist1 = ["a1", "a2"];
final List<String> _childOfParentlist1 = ["a31", "a32", "a34"];
final List<bool> _isSelectedForParentList1 = List.generate(
2,
(i) =>
false); // Fill it with false initially and this list for all the textList
final List<bool> _isSelectedForChildOfParentList1 =
List.generate(2, (i) => false);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ExpansionTile(
title: const Text('main a'),
children: [
ListView.builder(
itemBuilder: (_, i) {
return ListTile(
tileColor: _isSelectedForParentList1[i]
? Colors.blue
: null, // If current item is selected show blue color
title: Text(_parentlist1[i]),
onTap: () => setState(() => _isSelectedForParentList1[i] =
!_isSelectedForParentList1[i]), // Reverse bool value
);
},
),
ExpansionTile(
title: const Text('a3'),
children: [
ListView.builder(
itemBuilder: (_, i) {
return ListTile(
tileColor: _isSelectedForChildOfParentList1[i]
? Colors.blue
: null, // If current item is selected show blue color
title: Text(_childOfParentlist1[i]),
onTap: () => setState(() =>
_isSelectedForChildOfParentList1[i] =
!_isSelectedForChildOfParentList1[
i]), // Reverse bool value
);
},
),
],
),
],
),
);
}
Related
How do I make it so that the icon will only update for the tile that was clicked? Right now, the behavior is that all icons update when clicking on one tile.
Here is the code (trimmed to only include relevant parts):
Column(children: List.generate(
filteredFAQ.length,
(index) => Column(
children: [
if(index > 0) {
Container(
child: Column(
children: <Widget>[
ExpansionTile(
trailing: SvgPicture.string(
isQuestionClicked
? addPayeeArrowUp
: rightArrow,
color: primary,
),
onExpansionChanged:
(bool expanded) {
setState(() {
isQuestionClicked = expanded;
});
},
),
],
)
)
}
]
)
),);
here are screenshots of the behavior:
[2
I used the in built onExpansionChange of the ExpansionTile.
To only change the icon of the expanded tile, you can use this approach:
create a Map:
Map<int, bool> state = {};
and use it accordingly in your ExpansionTile to check whether it's selected, if the value is true or false:
List.generate(6, (index) {
return ExpansionTile(
title: Text('Item $index'),
trailing: state[index] ?? false
? Icon(Icons.arrow_drop_up)
: Icon(Icons.arrow_drop_down),
onExpansionChanged: (value) {
setState(() {
state[index] = value;
});
},
children: [
Container(
height: 100,
color: Colors.red,
),
],
);
}),
Complete runnable example:
import 'package:flutter/material.dart';
const Color darkBlue = 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
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Map<int, bool> state = {};
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Column(
children:
// generate 6 ExpansionTiles
List.generate(6, (index) {
return ExpansionTile(
title: Text('Item $index'),
trailing: state[index] ?? false
? Icon(Icons.arrow_drop_up)
: Icon(Icons.arrow_drop_down),
onExpansionChanged: (value) {
setState(() {
state[index] = value;
});
},
children: [
Container(
height: 100,
color: Colors.red,
),
],
);
}),
),
);
}
}
You have to manage each childrens state separatory.
I think it's best to manage them in filteredFAQ by adding
bool isExpanded
property there. but you can achive by manage them as separated property like
final items = List<bool>.generate(filteredFAQ.length, (index) => false);
and change their state when they're expanded
items[index] = !items[index]
here's a sample complete code
Column(children: List.generate(
filteredFAQ.length,
(index) => Column(
children: [
if(index > 0) {
Container(
child: Column(
children: <Widget>[
ExpansionTile(
trailing: SvgPicture.string(
items[index]
? addPayeeArrowUp
: rightArrow,
color: primary,
),
onExpansionChanged:
(bool expanded) {
setState(() {
items[index] = !items[index];
});
},
),
],
)
)
}
]
)
),);
And don't forget to initalize items at where you initialize filteredFAQ
If you provide a whole code in the widget I can complete it if you need more information
I'm new to flutter and I'm currently working on an app for my school project. At the moment I cannot make my expanded panel editable, could anybody tell me how I can make something editable or if it's not possible tell me an alternativ solution? Here's the code btw.:
class MyItem {
MyItem({this.isExpanded = false, required this.header, required this.body});
bool isExpanded;
final String header;
final String body;
}
class fourthPage extends StatefulWidget {
#override
list createState() => list();
}
class list extends State<fourthPage> {
final List<MyItem> _items = <MyItem>[
MyItem(
header: "header1",
body: "text1"),
MyItem(
header: "header2",
body: "text2"),
MyItem(
header: "header3",
body: "text3"),
];
#override
Widget build(BuildContext context) {
return _buildPage();
}
Widget _buildPage() {
return SafeArea(
top: true,
child: Scaffold(
appBar: AppBar(
title: const Text("page4"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text(
'Assignments:',
style: TextStyle(fontSize: 35),
),
ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_items[index].isExpanded = !_items[index].isExpanded;
});
},
children: _items.map((MyItem item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return Text(item.header);
},
isExpanded: item.isExpanded,
body: Container(child: Text(item.body)));
}).toList(),
),
ElevatedButton(
child: const Text('Page 1'),
onPressed: () {
Navigator.of(this.context).push(MaterialPageRoute(
builder: (context) => const FirstPage()));
},
),
ElevatedButton(
child: const Text('Page 2'),
onPressed: () {
Navigator.of(this.context).push(MaterialPageRoute(
builder: (context) => const SecondPage()));
},
),
ElevatedButton(
child: const Text('Page 3'),
onPressed: () {
Navigator.of(this.context).push(MaterialPageRoute(
builder: (context) => const ThirdPage()));
},
),
],
),
),
));
}
}
I have thought about using onclick in combination with a function but I just can't do it If anybody could help me I would really appreciate it
Below is my code.
var choices = ['Settings', 'Log Out'];
void choiceAction(String choice){
print(choice);
}
Widget aapBarSection(String title, Color color, BuildContext context){
return AppBar(
title: Text(title, style:TextStyle(fontFamily: 'Poppins-Regular'), ),
centerTitle: true,
backgroundColor: color,
actions: [
PopupMenuButton<String>(
onSelected: choiceAction,
itemBuilder: (BuildContext context){
return choices.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
)
],
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
exit(0);
},
),
);
}
menu items are showing but how to show image/icon as per the items like for settings(settings icon) and for logout(logout icon) with it?
Can anybody help me please!
You can use ListTile as child or Row.
PopupMenuItem<String>(
value: choice,
child: ListTile(
leading: Icon(Icons.work), // your icon
title: Text(choice),
),
)
var choices = [
{'title': 'Home', 'icon': const Icon(Icons.home)},
{'title': 'Profile', 'icon': const Icon(Icons.people)},
{'title': 'Logout', 'icon': const Icon(Icons.logout)}
];
var actions = [
PopupMenuButton<String>(
onSelected: choiceAction,
itemBuilder: (BuildContext ctx) {
return choices.map((ch) {
return PopupMenuItem<String>(
value: ch['title'].toString(),
child: ListTile(
leading: ch['icon'] as Widget,
title: Text(ch['title'].toString())));
}).toList();
})
];
void choiceAction(String choice){
print(choice);
}
class _HomeVendorPageState extends State<HomeVendorPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: actions,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [])));
}
}
Okay, so I think I am stuck with flutter builder a little bit.
I've created simple app, just to make my question easier:
I have a data class:
class DataLists {
List<ListTile> lists = [
ListTile(
leading: Text('Tile Leading 1'),
title: Text('Tile Title 1'),
subtitle: Text('Tile Subtitle 1'),
trailing: Text('Tile Trailing 1'),
),
ListTile(
leading: Text('Tile Leading 2'),
title: Text('Tile Title 2'),
subtitle: Text('Tile Subtitle 2'),
trailing: Text('Tile Trailing 2'),
),
ListTile(
leading: Text('Tile Leading 3'),
title: Text('Tile Title 3'),
subtitle: Text('Tile Subtitle 3'),
trailing: Text('Tile Trailing 3'),
),
ListTile(
leading: Text('Tile Leading 4'),
title: Text('Tile Title 4'),
subtitle: Text('Tile Subtitle 4'),
trailing: Text('Tile Trailing 4'),
),
ListTile(
leading: Text('Tile Leading 5'),
title: Text('Tile Title 5'),
subtitle: Text('Tile Subtitle 5'),
trailing: Text('Tile Trailing 5'),
),
];
}
And main dart file:
import 'package:flutter/material.dart';
import 'package:learning/data.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TestTile(),
);
}
}
class TestTile extends StatefulWidget {
#override
_TestTileState createState() => _TestTileState();
}
class _TestTileState extends State<TestTile> {
DataLists dataLists = DataLists();
TextEditingController leadingController = TextEditingController();
TextEditingController titleController = TextEditingController();
TextEditingController subtitleController = TextEditingController();
TextEditingController trailingController = TextEditingController();
Future<String> createDialog(BuildContext context) {
return showDialog(context: context, builder: (context) {
return SimpleDialog(
title: Text('Input data: '),
children: [
TextField(
controller: leadingController,
),
TextField(
controller: titleController,
),
TextField(
controller: subtitleController,
),
TextField(
controller: trailingController,
),
MaterialButton(
child: Text('Submit'),
onPressed: () {
Navigator.of(context).pop(leadingController.text);
setState(() {
List<ListTile> tempList = dataLists.lists;
if (titleController.text.isNotEmpty && leadingController.text.isNotEmpty && subtitleController.text.isNotEmpty && trailingController.text.isNotEmpty) {
tempList.add(
ListTile(
leading: Text(leadingController.text),
title: Text(titleController.text),
subtitle: Text(subtitleController.text),
trailing: Text(trailingController.text),
),
);
dataLists.lists = tempList;
} else {
print('Null values');
}
leadingController.clear();
titleController.clear();
subtitleController.clear();
trailingController.clear();
});
},
),
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test Tile'),
),
body: Container(
child: SafeArea(
child: ListView(
children: <ListTile>[
for (ListTile e in dataLists.lists)
e
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
createDialog(context);
setState(() {
});
},
child: Icon(Icons.add),
backgroundColor: Colors.blue,
),
);
}
}
The problem is: I cannot make it work in other way. Can someone change my implementation to a ListView.builder? I am stuck a little bit :(
Key goal:
Idea:
Click on a button -> form appear -> after you press a submit button list is updated instantly
I'll add a delete function later, just learning docs, nothing more.
Can someone review my code and, if no one mind, try to rewrite the same idea, but using ListView.builder?
I've tried several times, but cannot get properties correctly from the form, and update listtile using builder, need help
Cheers!
ListView.builder requires a static height, so keep a track on that one. Now, coming to the question, that you want to use ListView.builder. You can do via this
Container(
height: give_your_height,
child: ListView.builder(
shrinkWrap: true,
itemCount: dataLists.lists.length,
itemBuilder: (context, index) {
return dataLists.lists[index];
}
)
)
Try this, it may solve your issue.
ListView(
children: [
for (ListTile e in dataLists.lists)
Card(child: e)
],
),
or with ListView.builder()
ListView.builder(
itemCount: dataLists.lists.length,
itemBuilder: (context, index) {
return dataLists.lists[index];
},
);
Further Reference: https://api.flutter.dev/flutter/material/ListTile-class.html
I used a bottom navigation bar in flutter using this widget,
how can I make that bottom navigation bar show on all the pages?
and can I make it appear when I choose a page from drawer??
please help me,
You can actually achieve this with the pageview widget
https://api.flutter.dev/flutter/widgets/PageView-class.html
With this, you can have all the pages inside one class and build the bottom navigation bar underneath the pageview widget. By default the pages are swipeable but you can disable it doing
Scaffold(
body:
Container(
child:
Column(
children: <Widget> [
PageView(
physics:new NeverScrollableScrollPhysics())
controller: _controller,
children: [
MyPage1(),
MyPage2(),
MyPage3(),
],
),
googleNavBar()
]
)
);
May I suggest you to use flutter builtin BottomNavigationBar widget instead of third party widget.
Here is my code you can modify as per you requirement. Hope this will help.
class DashboardScreen extends StatefulWidget {
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> with SingleTickerProviderStateMixin {
final _selectedItemColor = Colors.white;
final _unselectedItemColor = Color(0xFF828282);
final _selectedBgColor = Color(0xFF00cde7);
final _unselectedBgColor = Colors.transparent;
int _currentIndex = 0;
GlobalKey<ScaffoldState> _key = GlobalKey();
// List of body of current screen you import/create from other dart file.
final List<Widget> _children = [
HomeScreen(),
AppointmentScreen(id: 1),
PaymentScreen(id: 1),
ProfileScreen(id: 1)
];
// List of dynamic app bar for different page. You can also import/create app bar easily
final List<Widget> _childAppBar = [
HomeAppBar(),
AppointmentAppBar(),
PaymentAppBar(),
ProfileAppBar()
];
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
debugPrint("Tapped item : $index");
}
Color _getBgColor(int index) =>
_currentIndex == index ? _selectedBgColor : _unselectedBgColor;
Color _getItemColor(int index) =>
_currentIndex == index ? _selectedItemColor : _unselectedItemColor;
Widget _buildIcon(IconData iconData, String text, int index) => Container(
width: MediaQuery.of(context).size.width,
height: kBottomNavigationBarHeight,
child: Material(
color: _getBgColor(index),
child: InkWell(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
child: Column(
children: [
Icon(iconData, color: _getItemColor(index)),
Text(text,
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, fontFamily: 'Poppins', color: _getItemColor(index))),
],
),
),
],
),
onTap: () => _onItemTapped(index), // function responsible for navigation on tap
),
),
);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
key: _key,
appBar: _childAppBar.elementAt(_currentIndex), // this is dynamic app bar
body: _children.elementAt(_currentIndex), // this is dynamic body of the current screen
bottomNavigationBar:
BottomNavigationBar(
currentIndex: 0,
type: BottomNavigationBarType.fixed,
iconSize: 30.0,
items: [
BottomNavigationBarItem(
icon: _buildIcon(Icons.home, "Home", 0), // Check this _buildIcon function above
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: _buildIcon(Icons.group, "Appointment", 1),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: _buildIcon(Icons.add_circle_outline, "Make Payment", 2),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: _buildIcon( Icons.person_outline, "My Account", 3),
title: SizedBox.shrink(),
),
]
),
drawer: _currentIndex == 0 || _currentIndex == 3 ? Drawer( // check to show drawer on particular screen
child: ListView(
padding: const EdgeInsets.all(0.0),
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("Mohammad Gayasuddin"),
accountEmail: Text("ladla8602#gmail.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.white70,
)),
ListTile(
title: Text('Login'),
trailing: Icon(Icons.lock),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
}),
ListTile(
title: Text('Sign Up'),
trailing: Icon(Icons.add_circle_outline),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterScreen(),
),
);
})
],
),
) : PreferredSize(
child: Container(),
preferredSize: Size(0.0, 0.0),
),
),
);
}
}