In this app, when the user clicks the FAB, it returns a Card wrapped in a GestureDetector. when the user clicks on a displayed card, the GestureDetector will navigate them to another page. I want to implement a delete function so that i can dismiss a Card.
So i defined the child: of the Card as a ListTile with a trailing Icon which is supposed to delete that particular Card. But when i add a Card, it only displays the title: and does not display the trailing Icon.
The Cards are displayed in a SliverGrid with a crossAxisCount = 2.
question: do Card widgets support displaying a ListTile with a trailing widget or is my implementation wrong?
P.S. i have tried setting crossAxisCount = 1 but it still does not show the trailing Icon.
card generating function:
void addItems() async {
setState(() {
cardList.insert(0, new GestureDetector(
onTap: () async {
await Navigator.push(context, MaterialPageRoute(
builder: (context) =>
TodoList(), // this just navigates to another screen ; not important in this question
)
);
},
child: Card(
child: ListTile(
title: Text("project 1"),
trailing: new Icon(Icons.remove_circle,
color: Colors.redAccent,),
// subtitle: whitefontstylemont(text: "project 1", size: 20,)) //this is just a custom TextStyle
),
)
));
});
}
card deleting function:
_deleteNoDo(int index) async {
debugPrint("Deleted Item!");
setState(() {
cardList.removeAt(index);
});
}
complete example (excluding above mentioned functions):
class _Starting_screenState extends State<Starting_screen> {
int _count = 0;
#override
Widget build(BuildContext context) {
List<Widget> cardList = new List.generate(
_count, (int i) => new createCard());
SystemChrome.setEnabledSystemUIOverlays([]);
// _deleteNoDo(int index) async {
// debugPrint("Deleted Item!");
// setState(() {
// cardList.removeAt(index);
// });
// }
//
// void addItems() async {
// setState(() {
// cardList.insert(0, new GestureDetector(
// onTap: () async {
// await Navigator.push(context, MaterialPageRoute(
// builder: (context) =>
// TodoList(), // this just navigates to another screen ; not important in this question
// )
// );
// },
// child: Card(
// child: ListTile(
// title: Text("project 1"),
// trailing: new Icon(Icons.remove_circle,
// color: Colors.redAccent,),
//// subtitle: whitefontstylemont(text: "project 1", size: 20,)) //this is just a custom TextStyle
// ),
// )
// ));
// });
// }
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
setState(() {
_count += 1;
});
},
heroTag: "btn2",
child: Icon(Icons.add, color: Color(whitecolor),), // this is just a custom color
backgroundColor: Color(redcolor),), // this is just a custom color
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
flexibleSpace: FlexibleSpaceBar(
),
actions: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 20, right: 10),
child: whitefontstyle(text: "Remaining tasks for today - ${cardList.length}", size: 20,), // this is just a custom textstyle
),
),
],
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: new SliverChildBuilderDelegate((context,
index) {
return cardList[index]; // this is where the cards are displayed in a list
},
childCount: cardList.length
)
),
]
)
);
}
}
actual result:
expected result (assume only displaying title and trailing icon as shown below):
Are you sure that you are calling the right methods. Cause there are few things which are not in place or you haven't shared the right code. I will address them below.
First of all the alignment of child text in card. It is in center of the card but there is no center property used in your code. Card widget does not auto align the text.
Second adding the items. You post the code of addItems function but also added another function in build with name createCard. So we don't know if that createCard have the same widget tree as the addItems function or not.
Its not working not so you are not seeing it but even if you are able to see the Icon widget you still won't be able to delete the item. The reason being is its a non-clickable widget. You should be using the IconButton widget if you want to add the click functionality on it.
These are my finding in your code. If you can review them or either share the correct code, may be I am able to help more.
Related
I have a simple list of ListTile s that I nested in Slidable widget using the flutter_slidable package. However there is one issue that when I use the delete button, the item stays on the screen, even though it is successfully removed from the list (If i make a hot reload, it will actually get removed from screen). I tried to add a setState method inside the onPressed function but I cannot add it because the SlidableAction widget is a stateless widget. How can I make the item disappear when I tap on this button?
Here is a small video demonstration. I delete two lists with the delete button. They stay on the screen. I go home screen and come back to see they got deleted.
https://streamable.com/td7blf
Here is my code:
Expanded(
child: ListView.builder(
itemCount: likedNames.length,
itemBuilder: (context, index) {
return Slidable(
key: const ValueKey(0),
endActionPane: ActionPane(
motion: ScrollMotion(),
dismissible: DismissiblePane(onDismissed: () {
likedNames.remove(likedNames[index]);
}),
children: [
SlidableAction(
onPressed: (context) {
likedNames.remove(likedNames[index]); // <- this is the part where I want to do the removing of the item
},
label: 'Delete',
backgroundColor: AppColors.error,
),
],
),
child: ListTile(
onTap: () {},
title: Text(likedNames[index].name),
trailing: Icon(Icons.chevron_right),
),
);
}),
),
onPressed: (context) {
setState() => likedNames.remove(likedNames[index])
},
I've used this solution:
onPressed: (BuildContext context) async {
await Provider.of<MyList>(context, listen: false)
.remove(MyList[index]);
}
MyList is a separate class:
class MyList extends ChangeNotifier
Whit this methods:
final List<String> _myList = [];
List<String> get items => _myList;
.
.
.
Future<bool> remove(String item) async {
_myList.remove(item);
notifyListeners();
return true;
}
I am very new to flutter and was just curious to know how can we create a new card widget everytime a button (lets say FAB) is clicked.
Suppose this is the card widget :
return Card(
child: Column(
children: [
Text('name'),
Text('standard'),
Text('Roll No'),
],
),
);
I want the cards to build with the same content everytime the FAB is clicked. Can someone help me with this ?
First declare a List of widget type
List<Widget> _cardList = [];
Then create your widget which you want to add on button click, e.g.
Widget _card() {
return Card(
child: Column(
children: [
Text('name'),
Text('standard'),
Text('Roll No'),
],
),
);
}
add your widget in List on button click
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
_cardList.add(_card());
});
},
),
Now use a ListView.builder for create a list of widget
ListView.builder(
shrinkWrap: true,
itemCount: _cardList.length,
itemBuilder: (context, index) {
return _cardList[index];
},
)
I have a ListView inside of a FutureBuilder. This FutureBuilder loads a list from disk and once loaded, shows a ListView. This ListView has a custom ItemTile to show for each loaded item. Each ItemTile has a button to copy itself (i.e. writing a copy of itself to memory). How can I now inform the ListView to be rebuild?
This is how my ItemTile is implemented (I only included the relevant code).
#override
Widget build(BuildContext context) {
return InkWell(
child: Card(
semanticContainer: false,
//This flag should be false if the card contains multiple different types of content.
child: Container(
height: 200,
width: double.infinity,
child:
Align(
alignment: Alignment.topRight,
child: getPopUpMenuButton(context),
)
),
),
);
}
PopupMenuButton<String> getPopUpMenuButton(BuildContext context) {
return PopupMenuButton(
onSelected: (str) {
switch (str) {
case 'MAKECOPY':
{
Item newItem = Item.clone(this.item, sameUID: false);
Scaffold.of(context).setState(() {
_itemManager.save(newItem);
});
}
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
value: 'MAKECOPY',
child: Text(AppLocalizations.of(context).get('edit_as_copy'))),
],
);
}
I assumed the Scaffold.of(context).setState would do the trick but it does not. How do I correctly call a rebuild of the ListView/FutureBuilder from a list item?
I have a list created using ReorderableListView. I want to have a separate each list item with a Divider. I am looking for a clean solution, similar to ListView.separated(), however I can't find anything similar for ReorderableListView. In my code at the moment I am using a column widget to which I add a divider for every item but this is very "hacky". Do you know how this could be implemented in a better way?
I'm looking for divider like here:
My Code:
Main Page:
Widget _buildList(RoutinesState state) {
if (state is RoutinesFetched || state is RoutinesReordered) {
List<CustomCard> cards = [];
state.routines.forEach(
(e) {
cards.add(
CustomCard(
key: PageStorageKey(UniqueKey()),
title: e.name,
onTap: () {
Navigator.pushNamed(
context,
RoutineDetails.PAGE_ROUTE,
arguments: RoutineDetailsArgs(e),
);
},
includeDivider: cards.isNotEmpty,
),
);
},
);
return ReorderableListView(
children: cards,
onReorder: (int from, int to) => {
bloc.add(ReorderRoutine(fromIndex: from, toIndex: to)),
},
);
}
return Container(
child: Text("No routines found yet :( "),
);
}
Custom Card Widget:
#override
Widget build(BuildContext context) {
List<Widget> columnItems = [];
if (this.includeDivider) {
columnItems.add(Divider());
}
columnItems.add(ListTile(
leading: CircleAvatar(
child: Icon(Icons.fitness_center),
),
title: Text(this.title),
subtitle: Text("Weights"),
trailing: ActionChip(
label: Icon(Icons.play_arrow),
backgroundColor: Color(0xffECECEC),
onPressed: () => null,
),
onTap: this.onTap,
));
return Column(
mainAxisSize: MainAxisSize.min,
children: columnItems,
);
}
You're right with using a Divider as it's usually used in ListTiles to serve as its name implies: a divider. Using a Column to define the Widgets in your ListTile isn't "hacky", you're just defining the Widgets in the ListTile the way it can be used. Also, since the Divider is added in the ListTile, when the tile is dragged, the Divider will be moved along with the entire ListTile as expected.
Have you attempted to decorate the container you are using with?
This suggestion by #J. S. worked for me like a charm. No need for a devider when you can just paint the bottom border.
First I tried it with Divider() but that didn't look right because the ListTile is the "tappable" area and when you append a Divider below that, there is space that isn't considered tappable. So there is a white border above the divider that isn't painted with the splash effect. Also when you drag the Tile, it looked as if it would cut some of the lower tile with itself. This is what worked for me:
ReorderableListView.builder(
itemCount: _timeSegments!.length,
itemBuilder: (ctx, index) {
return Container(
key: Key(_timeSegments![index].id),
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Theme.of(context).dividerColor, width: 0.5))),
child: TimeSegmentTile(
timeSegment: _timeSegments![index],
press: () => Navigator.pushNamed(context, '/EditTimeSegmentScreen', arguments: EditTimeSegmentScreenArguments(_timeSegments![index], _timeSegmentRepository)),
),
);
},
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final TimeSegment item = _timeSegments!.removeAt(oldIndex);
_timeSegments!.insert(newIndex, item);
});
},
),
When the user clicks the FAB, i want it to create a new Card widget wrapped in a GestureDetector. I have created a list of type widget and also created a function with a setState to add to the list and display it in a SliverGrid. but when i click the FAB, it doesnt seem to be adding.
Could i get a small guide on how to add a Card using an FAB. (or adding any type of widget in a SliverGrid).
the list : List<Widget> cardList = <Widget>[];
function for adding items to the list :
void addItems() async{
setState(() {
cardList.insert(0, GestureDetector(
onTap: () async {
await Navigator.push( context, MaterialPageRoute(
builder: (context) => TodoList(), // this just navigates to another screen ; not important in this question
)
);
},
child: Card(
child:Center(child: whitefontstylemont(text: "project 1", size:20,)) //this is just a custom TextStyle
),
));
});
}
the floating action button which adds a new Card to the list :
floatingActionButton: FloatingActionButton( // this is inside a Scaffold
onPressed: ()async {
setState(() {
addItems();
});
},
heroTag: "btn2",
child: Icon(Icons.add, color: Color(whitecolor),), backgroundColor: Color(redcolor),),
the SliverGrid where i want a new card to be displayed :
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: new SliverChildBuilderDelegate((context, index) {
return cardList[index];
},
childCount: cardList.length // this is where i want a new Card to be displayed
)
),