I'm trying to implement a solution where a row (containing both a TextField and a Text) in ListView.Builder is automatically for every record retrieved from a webserver.
However when I want to start typing in such a TextField the keyboard appears and immediatly disappears again.
This is the code of my screen.
class GameScreen extends StatelessWidget {
static const RouteName = "/GameScreen";
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
const horizontalMargin = 20.0;
return Scaffold(
appBar: getAppBar(),
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
Background(),
Column(
children: <Widget>[
Header("Starting letter: B"),
Expanded(
child: ListBlocProvider(
listWidget: GameCategoriesList(),
itemsService: CategoriesService(),
margin: EdgeInsets.only(
left: horizontalMargin,
bottom: 10,
right: horizontalMargin,
),
),
),
SizedBox(
height: 20,
),
SizedBox(
width: size.width - 40,
height: 60,
child: Container(
height: 60,
child: TextButtonWidget(
() {
// Navigator.of(context).pushNamed(GameScreen.RouteName);
},
"Stop game",
),
),
),
SizedBox(
height: 20,
)
],
),
],
),
);
}
}
This is the code of my ListBlocProvider:
class ListBlocProvider extends StatelessWidget {
final ListWidget listWidget;
final ItemsService itemsService;
final bool useColor;
final bool usePaddingTop;
final double height;
final EdgeInsets margin;
const ListBlocProvider({
#required this.listWidget,
#required this.itemsService,
this.useColor = true,
this.usePaddingTop = true,
this.height = 200,
this.margin,
});
#override
Widget build(BuildContext context) {
const horizontalMargin = 20.0;
return BlocProvider(
create: (context) => ItemsBloc(itemsService: itemsService)..add(ItemsFetched()),
child: Container(
padding: usePaddingTop ? EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: this.useColor ? Color.fromRGBO(10, 50, 75, 0.9) : null,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
margin: this.margin,
height: this.height,
child: this.listWidget,
),
);
}
}
This is the code of my List:
class GameCategoriesList extends ListWidget {
#override
_GameCategoriesListState createState() => _GameCategoriesListState();
}
class _GameCategoriesListState extends State<GameCategoriesList> {
#override
Widget build(BuildContext context) {
return BlocBuilder<ItemsBloc, ItemsState>(
builder: (context, state) {
if (state is ItemsFailure) {
return Center(
child: Text('failed to fetch categories'),
);
}
if (state is ItemsSuccess) {
if (state.items.isEmpty) {
return Center(
child: Text('no categories found.'),
);
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
var textEditingController = TextEditingController();
return GameCategoryItemWidget(
key: UniqueKey(),
categoryModel: state.items[index],
textEditingController: textEditingController,
);
},
itemCount: state.items.length,
);
}
return Center(
child: LoadingIndicator(),
);
},
);
}
}
And this is the code where the both the TextField and the Text are build:
class GameCategoryItemWidget extends StatefulWidget {
final CategoryModel categoryModel;
final TextEditingController textEditingController;
const GameCategoryItemWidget({Key key, this.categoryModel, this.textEditingController}) :
super(key: key);
#override
_GameCategoryItemWidgetState createState() => _GameCategoryItemWidgetState();
}
class _GameCategoryItemWidgetState extends State<GameCategoryItemWidget> {
var formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: this.formKey,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 20, bottom: 10),
child: Text(
this.widget.categoryModel.name,
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
Container(
color: Colors.white,
child: InputField(
InputDecoration(labelText: this.widget.categoryModel.name),
this.widget.textEditingController,
false,
),
),
],
),
),
);
}
#override
void dispose() {
this.widget.textEditingController.dispose();
super.dispose();
}
}
The InputField is a custom widget to hide the switch between a Material and a Cupertino version of the TextField.
I've already tried to remove the Key from the custom TextField widget. The funny part is that the input is actually working, however it can't determine for which of the TextFields in the ListView the input is determined so it adds the input to all of them. I've also tried to swap things around with making Stateless widgets Statefull, but that didn't help either.
The entire build is based upon: https://bloclibrary.dev/#/flutterinfinitelisttutorial.
Hoping you guys can help me.
Related
I need some help when implementing the search bar funcionality in Flutter.
I am implementing flappy_search_bar: https://pub.dev/packages/flappy_search_bar
However, it does return any value when I try to search something.
Is there anything I am missing? Seems trivial this kind of implementations, just query some list and include the results in other list but I cannot figure out the way to do it.
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
TextEditingController textController = TextEditingController();
Future<List<dynamic>> search(String search) async {
await Future.delayed(Duration(seconds: 2));
List<dynamic> dogs = BreedList.where((dog) => dog['breed'].contains(search)).toList();
return dogs;
// return List(search.length, (int index) {
// return DogClass(breed: "$search $index");
// });
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(children: [
Container(
margin: const EdgeInsets.only(top: 60, bottom: 15),
padding: const EdgeInsets.only(left: 20, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Center(
child: Container(
width: MediaQuery.of(context).size.width -
(MediaQuery.of(context).size.width / 3.5),
height: 80,
child: SearchBar<dynamic>(
searchBarStyle: SearchBarStyle(borderRadius: BorderRadius.circular(20)),
onSearch: search,
cancellationWidget: Text('Cancel'),
emptyWidget: SizedBox.shrink(),
shrinkWrap:true,
onItemFound: (dynamic dogs, int index) {
return Container(
child: ListTile(
title: Text(dogs.breed.toString())
),
);
}),
//child: const Icon(Icons.search, color: Colors.white),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(Dimensions.radius20),
color: Colors.white),
),
)
],
),
),
//wrapping with this widgets the scroll problem is solved in list
const Expanded(
child: SingleChildScrollView(
child: BookPageBody(),
)),
]));
}
}
I have list of products in bottom sheet, when I choose any product I want to parent's widget to add it, unfortunately my product adds only after hot reload, or when I create a new route from bottom sheet to parent's widget, how can I solve this problem, any ideas? Here is the part of the bottom sheet code
class IceBottomSheet extends StatefulWidget {
const IceBottomSheet({Key? key}) : super(key: key);
#override
_IceBottomSheetState createState() => _IceBottomSheetState();
}
class _IceBottomSheetState extends State<IceBottomSheet> {
final _model = ProductWidgetsModel();
#override
Widget build(BuildContext context) {
List<Widget> productWidgetList = [];
products.forEach((product) =>
productWidgetList.add(SingleProductWidget(product: product)));
return Provider(
model: _model,
child: Expanded(
child: GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 16,
shrinkWrap: true,
crossAxisCount: 2, children: productWidgetList),
),
);
}
}
class SingleProductWidget extends StatefulWidget {
final Product product;
const SingleProductWidget({Key? key, required this.product})
: super(key: key);
#override
State<SingleProductWidget> createState() => _SingleProductWidgetState();
}
class _SingleProductWidgetState extends State<SingleProductWidget> {
#override
Widget build(BuildContext context) {
final model = Provider.of(context)?.model;
return Padding(
padding: const EdgeInsets.all(5.0),
child:
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: double.infinity,
height: 100,
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: widget.product.image,
),
shape: BoxShape.circle,
border: Border.all(
color: model?.idSelected == widget.product.id
? Colors.yellow
: Colors.grey,
width: 5.0,
style: BorderStyle.solid,
),
),
child: GestureDetector(
onTap: () {
model?.idSelected = widget.product.id;
// Route route =
// MaterialPageRoute(builder: (context) => BerryPage(context,));
// Navigator.push(context, route);
if(model?.idSelected == 1){
menuRow.removeAt(2);
Navigator.pop(context);
choice.insert(2, Adds(id: 102, name: 'Холодок', img: 'https://autogear.ru/misc/i/gallery/73434/2759438.jpg'));
}
}),
),
),
And here is the part of parent's widget code, it is inside GestureDetector
else if (index == 2){
setState(() {
});
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext builder) {
return Container(
height: 250,
child: Column(
children: [
SizedBox(
height: 10,
),Row(
children: [
Padding(
padding: EdgeInsets.only(left: MediaQuery.of(context).size.width*0.25 + MediaQuery.of(context).size.width *0.12),
child: Text(
'Холодок',
style: TextStyle(
fontFamily: 'Newfont',
fontSize: 22,
),
),
),
SizedBox(width: MediaQuery.of(context).size.width*0.25,),
IconButton(icon: Icon(Icons.close),onPressed: (){Navigator.pop(context);},)
],
),
Divider(),
IceBottomSheet(),
],
));
},
);
So when you open the BottomSheet you have to add await before it, so when you call Navigator.pop(context, data_you_want_to_pass_to_parent) it will wait for some data to be returned.
final data = await openBottomSheet();
inside the bottomSheet when you want to close, just pass the the desired data as so
Navigator.pop(context, data_you_want_to_pass_to_parent);
I'm trying to create a horizontallistview inside a verticalistview which is part of draggablescrollablesheet, confused? Basically I'm trying to create something similar to this page Horizontal ListView inside a Vertical ScrollView in Flutter, but inside a draggablescrollablesheet. So far I keep on running to errors from incorrectparentwidget to unbounded height problems, I tried searching everywhere but no solutions so far if anyone can help that would be helpful. Here is the code
main.dart
class Weather extends StatefulWidget {
const Weather({Key? key}) : super(key: key);
#override
_WeatherState createState() => _WeatherState();
}
class _WeatherState extends State<Weather> {
late image_switch testing;
late time_identifier time;
late weather_screen_data data_weather;
late weather_data weather;//the method to get the variable below
late String iconurl;
late String image;
late String timeofday;
late String year;
late String weekdate;
late String weekday;
late String month;
#override
void initState() {
super.initState();
testing = image_switch();
time = time_identifier();
initializeDateFormatting();
testing.check_time();
setState(() {
timeofday = time.time_switcher();
image = testing.image_switcher();
year = time.year();
weekdate = time.weekdate();
weekday = time.weekday();
month = time.month();
});
}
Future<weather_screen_data> data= weather_data.get_data();
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<weather_screen_data>(
future: data,
builder: (BuildContext context, AsyncSnapshot<weather_screen_data> snapshot) {
if (snapshot.hasData) {
return Stack(
fit: StackFit.expand,
children: [
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(image),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.3),
BlendMode.darken),
)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
margin: EdgeInsets.only(top: 30.h, left: 11.w),
child: Text("Selamat $timeofday, Dwika",
style: TextStyle(
fontSize: 20.sp,
color: Colors.white,
))),
Container(
margin: EdgeInsets.only(top: 310.h, left: 23.w),
child: Text("${snapshot.data!.description.variable}",
style: TextStyle(
fontSize: 48.sp,
color: Colors.white,
))),
Container(
margin: EdgeInsets.only(top: 22.h, left: 23.w),
child: Text(weekday,
style: TextStyle(
fontSize: 34.sp,
color: Colors.white,
))),
Container(
margin: EdgeInsets.only(top: 22.h, left: 23.w),
child: Text("$weekdate $month",
style: TextStyle(
fontSize: 34.sp,
color: Colors.white,
))),
Container(
margin: EdgeInsets.only(top: 22.h, left: 23.w),
child: Text("$year",
style: TextStyle(
fontSize: 34.sp,
color: Colors.white,
)))
])),
Positioned(
top:82.h,
left:12.w,
child: Container(
width: 160.w,
height: 160.w,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
image: DecorationImage(
image:NetworkImage("http://openweathermap.org/img/w/" + snapshot.data!.icon.variable + ".png"),
fit:BoxFit.cover),
)),
),
Positioned(
top: 220.h,
left: 12.w,
child: Row(
children: [
Text("${snapshot.data!.temprature.variable.round()}°",
style: TextStyle(fontSize: 96, color: Colors.white)),
Text("C",
style: TextStyle(
fontSize: 96,
color: Colors.white,
)),
],
),
),
Positioned(
top: 21.h,
right: 30.w,
child: IconButton(
iconSize: 40.sp,
color: Colors.white,
icon: Icon(Icons.refresh),
onPressed: () {
time_identifier().generate_days();
setState(() {
data=weather_data.get_data();
testing.check_time();
image = testing.image_switcher();
timeofday = time.time_switcher();
image = testing.image_switcher();
year = time.year();
weekdate = time.weekdate();
weekday = time.weekday();
month = time.month();
});
},
)),
bottomsheet()
]);
}else{
return Center(
child:CircularProgressIndicator()
);
}
}
));
}
}
just focus on the part where I put the bottomsheet, you can ignore the other parts
bottomsheet.dart
class bottomsheet extends StatefulWidget {
const bottomsheet({Key? key}) : super(key: key);
#override
_bottomsheetState createState() => _bottomsheetState();
}
class _bottomsheetState extends State<bottomsheet> {
#override
// void initState() {
// super.initState();
// // days=time_identifier().generate_days();
// setState(() {
// days=time_identifier().generate_days();
// });
// }
#override
Widget build(BuildContext context) {
List<String> days = time_identifier().generate_days();
return DraggableScrollableSheet(
initialChildSize:0.06,
minChildSize:0.05,
maxChildSize:0.8,
builder: (BuildContext context, ScrollController scrollController)
{
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(50),
topRight: Radius.circular(50),
)
),
child: ListView.builder(itemCount:5,itemBuilder: (BuildContext context, int index) {
return
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children:[
Expanded(
child: ListView.builder(shrinkWrap:true,
scrollDirection:Axis.horizontal,itemCount:5,itemBuilder:(BuildContext context, int index){
return Text("Hello World");
}),
)
]),
);
},)
);},
);
}
}
As you can see I have tried everything in the book, wrapping the widget with Expanded, and then using Columns, shrinkwrap,etc but it always gives me incorrect ParentWidget Use or unbounded height problems.
After much fidgeting around it turns out I have to wrap my ListView.builder inside a container so it becomes like this in the bottomsheet.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../weather/time_identifier.dart';
class bottomsheet extends StatefulWidget {
const bottomsheet({Key? key}) : super(key: key);
#override
_bottomsheetState createState() => _bottomsheetState();
}
class _bottomsheetState extends State<bottomsheet> {
#override
// void initState() {
// super.initState();
// // days=time_identifier().generate_days();
// setState(() {
// days=time_identifier().generate_days();
// });
// }
#override
Widget build(BuildContext context) {
List<String> days = time_identifier().generate_days();
return DraggableScrollableSheet(
initialChildSize:0.06,
minChildSize:0.05,
maxChildSize:0.8,
builder: (BuildContext context, ScrollController scrollController)
{
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(50),
topRight: Radius.circular(50),
)
),
child: ListView.builder(controller: scrollController,itemCount:5,itemBuilder: (BuildContext context, int index) {
return
Container(
height:200,
child: ListView.builder(shrinkWrap:true,
scrollDirection:Axis.horizontal,itemCount:10,itemBuilder:(BuildContext context, int index){
return Text("Hello World");
}),
);
},)
);},
);
}
}
In this code, when I change page (I'm using PageView as is it in code below) flutter doesn't trigger rebuild, so condition if(_page == 1) will take effect after I press "hot reload". Any tips for solution? I calling this class in main.dart (HomePage) which is Stateless widget. Could it be the problem?
Thanks for any help!
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
int _page = 0;
class Guide extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new GuideState();
}
}
class GuideState extends State<Guide> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: [
if (_page == 1)
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Image(
image: AssetImage('graphics/Logo.png'),
height: MediaQuery.of(context).size.height * 0.1)),
SizedBox(height: 500, child: Page()),
]))));
}
}
class Page extends StatefulWidget {
PageState createState() => PageState();
}
class PageState extends State<Page> {
final controller = PageController(
initialPage: 0,
);
#override
Widget build(BuildContext context) {
return Scaffold(
//appBar: AppBar(title: Text('PageView Widget in Flutter')),
body: Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.95,
height: MediaQuery.of(context).size.height * 0.6,
child: PageView(
controller: controller,
onPageChanged: (page) {
setState(() {
if (page == 1) {
_page = 1;
}
});
},
pageSnapping: true,
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
//color: Colors.pink,
//child: Center(
child: Text(
'1. Tento text bude nahrán z databáze.',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
Container(
//color: Colors.green,
child: Text(
'2. Tento text bude nahrán z databáze',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
Container(
// color: Colors.lightBlue,
child: Text(
'3. Tento text bude nahrán z databáze',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
],
),
)));
}
}
The variable _page is set as global, it has to be part of as state inorder to trigger changes, but in your case you want to change a widget base on action in another child widget, this can be done in several ways depending on your choice the easies in you case is to have a function as a parameter for your child widget Page :
class Page extends StatefulWidget {
final Function(int) onChange;
const Page({Key key, this.onChange}) : super(key: key);
PageState createState() => PageState();
}
and then call it when the page change
onPageChanged: (page) {
widget.onChange(page);
},
so with this you can handle the change in you parent widget and trigger state change
class GuideState extends State<Guide> {
int _page = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children:[
if (_page == 1)
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Image(
image: AssetImage('graphics/Logo.png'),
height: MediaQuery.of(context).size.height * 0.1,
),
),
SizedBox(
height: 500,
child: Page(
onChange: (page) {
setState(() => _page = page);
},
),
),
],
),
),
),
);
}
}
int _page = 0; is not part of the state of your Guide widget. Place it here:
class GuideState extends State<Guide> {
int _page = 0;
...
I've been trying to design the layout of my ExpansionTile just like the design below but I couldn't figure out how to change the layout. any suggestion on how to change the border radius, change the background color and also make a gap between each other?.
I tried adding boxDecoration in each container but the style only apply to outside but not on each expansionTile.
import 'package:flutter/material.dart';
class MyReoderWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ReorderItems(topTen: ['j']);
}
}
class DataHolder {
List<String> parentKeys;
Map<String, List<String>> childMap;
DataHolder._privateConstructor();
static final DataHolder _dataHolder = DataHolder._privateConstructor();
static DataHolder get instance => _dataHolder;
factory DataHolder.initialize({#required parentKeys}) {
_dataHolder.parentKeys = parentKeys;
_dataHolder.childMap = {};
for (String key in parentKeys) {
_dataHolder.childMap.putIfAbsent(
}
return _dataHolder;
}
}
class ReorderItems extends StatefulWidget {
final List<String> topTen;
ReorderItems({this.topTen});
#override
_ReorderItemsState createState() => _ReorderItemsState();
}
class _ReorderItemsState extends State<ReorderItems> {
#override
void initState() {
super.initState();
// initialize the children for the Expansion tile
// This initialization can be replaced with any logic like network fetch or something else.
DataHolder.initialize(parentKeys: widget.topTen);
}
#override
Widget build(BuildContext context) {
return PrimaryScrollController(
key: ValueKey(widget.topTen.toString()),
controller: ScrollController(),
child: Container(
decoration: BoxDecoration(),
child: ReorderableListView(
onReorder: onReorder,
children: getListItem(),
),
),
);
}
List<ExpansionTile> getListItem() => DataHolder.instance.parentKeys
.asMap()
.map((index, item) => MapEntry(index, buildTenableListTile(item, index)))
.values
.toList();
ExpansionTile buildTenableListTile(String mapKey, int index) => ExpansionTile(
key: ValueKey(mapKey),
title: Text(mapKey),
leading: Icon(Icons.list),
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20))
),
key: ValueKey('$mapKey$index'),
height: 200,
child: Container(
padding: EdgeInsets.only(left: 30.0),
child: ReorderList(
parentMapKey: mapKey,
),
),
),
],
);
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
setState(() {
String game = widget.topTen[oldIndex];
DataHolder.instance.parentKeys.removeAt(oldIndex);
DataHolder.instance.parentKeys.insert(newIndex, game);
});
}
}
class ReorderList extends StatefulWidget {
final String parentMapKey;
ReorderList({this.parentMapKey});
#override
_ReorderListState createState() => _ReorderListState();
}
class _ReorderListState extends State<ReorderList> {
#override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: ScrollController(),
child: ReorderableListView(
// scrollController: ScrollController(),
onReorder: onReorder,
children: DataHolder.instance.childMap[widget.parentMapKey]
.map(
(String child) => Container(
child: ListTile(
key: ValueKey(child),
leading: Icon(Icons.list),
title: Text(child),
),
),
)
.toList(),
),
);
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
List<String> children = DataHolder.instance.childMap[widget.parentMapKey];
String game = children[oldIndex];
children.removeAt(oldIndex);
children.insert(newIndex, game);
DataHolder.instance.childMap[widget.parentMapKey] = children;
// Need to set state to rebuild the children.
setState(() {});
}
}
You can do it using custom expandable container.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Calendar',
theme: ThemeData(
primarySwatch: Colors.grey,
),
debugShowCheckedModeBanner: false,
home: Material(
child: MyReoderWidget(),
),
);
}
}
class CustomModel {
String title;
bool isExpanded;
List<String> subItems;
CustomModel({this.title, this.subItems, this.isExpanded = false});
}
class MyReoderWidget extends StatefulWidget {
#override
_MyReoderWidgetState createState() => _MyReoderWidgetState();
}
class _MyReoderWidgetState extends State<MyReoderWidget> {
List<CustomModel> listItems;
#override
void initState() {
super.initState();
listItems = List<CustomModel>();
listItems.add(CustomModel(
title: "App Name 1", subItems: ["Card Name 1", "Card Name 2"]));
listItems.add(CustomModel(
title: "App Name 2", subItems: ["Card Name 3", "Card Name 4"]));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: ListView(
children: listItems
.map((model) => new Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: ExpandableCardContainer(
isExpanded: model.isExpanded,
collapsedChild: createHeaderCard(model),
expandedChild: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: createHeaderCard(model),
)
]..addAll(model.subItems
.map((e) => createChildCard(e))
.toList()),
),
),
))
.toList()),
);
}
Widget createHeaderCard(CustomModel model) {
return Container(
child: Row(
children: <Widget>[
Icon(
Icons.more_vert,
color: Colors.white,
),
Expanded(
child: Text(
model.title,
style: TextStyle(color: Colors.white),
),
),
GestureDetector(
onTap: () {
setState(() {
model.isExpanded = !model.isExpanded;
});
},
child: Icon(
model.isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.white,
),
)
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFF132435),
),
height: 50,
);
}
Widget createChildCard(String subItems) {
return Container(
margin: EdgeInsets.only(left: 30, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.more_vert,
color: Colors.white,
),
Expanded(
child: Text(
subItems,
style: TextStyle(color: Colors.white),
),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFF132435),
),
height: 50,
);
}
}
class ExpandableCardContainer extends StatefulWidget {
final bool isExpanded;
final Widget collapsedChild;
final Widget expandedChild;
const ExpandableCardContainer(
{Key key, this.isExpanded, this.collapsedChild, this.expandedChild})
: super(key: key);
#override
_ExpandableCardContainerState createState() =>
_ExpandableCardContainerState();
}
class _ExpandableCardContainerState extends State<ExpandableCardContainer> {
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: widget.isExpanded ? widget.expandedChild : widget.collapsedChild,
);
}
}