Animating item removal in a grid in Flutter - flutter

I have a grid of boxes using the Wrap widget (I did not use the GridView as this requires you stating how many items you need in each row ahead of time).
I want to remove the item when they are clicked, and have all the other items animate to their new position like this: https://vestride.github.io/Shuffle/adding-removing (click on the boxes to see what I mean).
Here is my code so far without any animation:
class Boxes extends StatefulWidget {
#override
_BoxesState createState() => _BoxesState();
}
class _BoxesState extends State<Boxes> {
var items = [
{'id': '25', 'name': 'A',},
{'id': '19', 'name': 'B',},
{'id': '35', 'name': 'C',},
{'id': '20', 'name': 'D',},
{'id': '958', 'name': 'E',},
{'id': '1278', 'name': 'F',},
{'id': '500', 'name': 'G',},
];
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
child: Wrap(
alignment: WrapAlignment.center,
children: [
for (final item in items)
Box(
key: Key(item['id']),
name: item['name'],
onDelete: () {
setState(() {
items.remove(item);
});
},
)
],
),
);
}
}
class Box extends StatelessWidget {
String name;
Function onDelete;
Box({this.name, this.onDelete, Key key}):
super(key: key);
Widget build(BuildContext context) {
return GestureDetector(
onTap: onDelete,
child: Container(
color: Colors.lightBlue,
width: 90,
height: 90,
margin: EdgeInsets.all(8),
child: Center(
child: Text(name),
)
),
);
}
}
The closest built-in widget I could find is AnimatedList, but that does not work with a grid.
I also tried animating the width of the deleted box to 0, which did not work as the other boxes just jump into position instead of animating to the new position.
How would I go about doing this?

Simple but effective:
Create an async Function that handles deletes and declares wait
delays
Call the delete function to handle independent deletes on each element
Animate using an AnimatedContainer and an AnimatedOpacity based on the state
Here is a working example using Chips as widgets:
Note: I'll leave for you to decide the max width of the Custom Widget "Box" you will use and if needed calculate its dynamic width depending on its content
//declare an empty list that handles items to be deleted
List<String> deleteItems = [];
//define async Function that handles progressive deletes from the list
void deleteItem(String id) async {
setState(() {
deleteItems.add(id);
});
Future.delayed(Duration(milliseconds: 250)).whenComplete(() {
setState(() {
deleteItems.removeWhere((i) => i == id);
items.removeWhere((i) => i["id"] == id);
});
});
}
//Widget to be used to animated the List/Wrap
return SizedBox.expand(
child: Wrap(
alignment: WrapAlignment.center,
children: List.generate(items.length, (index) {
var item = items[index];
bool isMarkedForDelete =
deleteItems.where((i) => i == item["id"]).isNotEmpty;
return AnimatedContainer(
key: ObjectKey(item),
duration: Duration(milliseconds: 250),
//alignment: Alignment.centerLeft,
width: isMarkedForDelete ? 0 : 60, //change depending on font size or content
child: AnimatedOpacity(
duration: Duration(milliseconds: 250),
opacity: isMarkedForDelete ? 0 : 1,
child: Chip(
label: Text("${item["name"]}"),
backgroundColor:
isMarkedForDelete ? Colors.red : Colors.blue,
deleteIcon: Icon(Icons.close),
onDeleted: () {
if(!isMarkedForDelete) deleteItem(item["id"] ?? "");
})));
}),
));
Geetings.

You could give a try to use AnimatedContainer
I basically change the width from 100 to 0, when the tap event occurs.
I also change the list items a little.
AnimatedContainer detects that the property width changed and fires the animation.
Please try something like this (is not the same as your reference)
I commented the items.remove(item), you need to find a way to really remove the item once the animation is finished (maybe with a timer or future).
List<Map<String,dynamic>> items = [
{'id':'11','name':'A','width':100},
{'id':'12','name':'B','width':100},
{'id':'13','name':'C','width':100},
{'id':'14','name':'D','width':100},
{'id':'15','name':'E','width':100},
{'id':'16','name':'F','width':100},
{'id':'17','name':'G','width':100},
];
Widget build(BuildContext context) {
return Scaffold(body: Container(
width: double.infinity,
height: double.infinity,
child: Wrap(
alignment: WrapAlignment.center,
children: [
for (final item in items)
AnimatedContainer(
width: double.parse(item['width'].toString()),
duration: Duration(milliseconds: 600),
curve: Curves.easeInBack,
child: Box(
key: Key(item['id']),
name: item['name'],
onDelete: () {
setState(() {
//items.remove(item);
item['name'] = "";
item['width'] = 0;
});
},
),
)
],
),
));
}
Flutter video --> https://www.youtube.com/watch?v=yI-8QHpGIP4

You can use AnimatedList
Widget's video:
https://www.youtube.com/watch?v=ZtfItHwFlZ8
Some docs:
https://api.flutter.dev/flutter/widgets/AnimatedList-class.html

Related

Flutter Google Map Markers are shown only after hot reload - using with cubit and custom marker

I'm struggling second day on this issue.
I use flutter google map to show about hundred custom markers with network image icons, that can be svg or png (using MarkerGenerator).
After opening the map page, MapCubit start to load items from API. In build i have BlocConsumer, where is listener, that build markers when loaded in that cubit and builder that build GoogleMap.
Problem is, that on first opening of page there are no images in markers, only white circle. When I tried to set one image url to all markers, it was loaded properly. Then, when i go on previous page or hot reload (not always), icons are there. On same page i have legend, that draw images from same urls, where images are set properly in most times. Sometimes it is need to go back and forward more times.
I can load icons after click on item in filter, that calls MapCubit, too.
I dont know, if it means something, but next problem, what i have is, that on release and appbundle build, no map is shown, only grey screen, buttons on side and google logo on bottom left.
I tried many tips on internet, but nothing helped.
Preview of first opening of MapPage
Preview of filter at first opening (has all icons)
Preview of second opening of MapPage
Preview of third opening of MapPage (has all icons)
MapPage (MarkerGenerator is in listener and initState becouse of two different uses that needs it)
class _MapAreaState extends State<MapArea> {
MapCubit _mapCubit;
Set<Marker> markers = {};
List<CustomMarker> markerWidgets = [];
bool markersLoaded = false;
#override
void initState() {
_mapCubit = BlocProvider.of<MapCubit>(context);
markers = {};
MarkerGenerator(
_mapCubit.state.items.map((e) => CustomMarker(type: e.type)).toList(),
(bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
color: tercialBackgroundColor,
child: BlocConsumer<MapCubit, MapState>(
bloc: _mapCubit,
listener: (context, state) {
if (state.changedItems && state.items.isNotEmpty) {
markerWidgets = _mapCubit.state.items
.map((e) => CustomMarker(type: e.type))
.toList();
markers = {};
MarkerGenerator(markerWidgets, (bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
log(bmp.toString());
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
}
},
builder: (context, state) {
return Stack(
children: [
GoogleMap(
zoomControlsEnabled: false,
compassEnabled: false,
markers: markers,
// markers: Set<Marker>.of(state.markers),
initialCameraPosition: CameraPosition(
target: state.items.length == 1
? state.items[0].latLng
: LatLng(49.07389317899512, 19.30980263713778),
zoom: 8.5,
),
minMaxZoomPreference: MinMaxZoomPreference(8, 22),
cameraTargetBounds: CameraTargetBounds(LatLngBounds(
northeast: LatLng(50.16477808289659, 20.56397637952818),
southwest: LatLng(48.75267922516721, 18.76330228064009),
)),
onMapCreated: (GoogleMapController controller) {
if (!_mapCubit.controller.isCompleted) {
rootBundle
.loadString('assets/googleMapsStyle.json')
.then((string) async {
controller.setMapStyle(string);
});
_mapCubit.controller.complete(controller);
log(_mapCubit.controller.toString());
log(controller.toString());
setState(() {
});
}
},
),
// if(state.items.isEmpty)
// FullScreenLoadingSpinner()
],
);
},
),
);
}
}
CustomMarker class
class CustomMarker extends StatelessWidget {
final ItemType type;
const CustomMarker({Key key, this.type}) : super(key: key);
#override
Widget build(BuildContext context) {
// precachePicture(
// svgPicture.pictureProvider,
// Get.context!,
// );
// if(type.icon is /-
return Stack(
clipBehavior: Clip.none,
children: [
Icon(
Icons.add_location,
color: type.color,
size: 56,
),
Positioned(
left: 16,
top: 10,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: primaryBackgroundColor,
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(child: type.icon),
),
),
),
],
);
}
}
Icon setting in ItemType factory, that is used in CustomMarker
icon: map['icon'] != null
? (map['icon'] is Image
? map['icon']
: (map['icon'].substring(map['icon'].length - 4) == '.svg'
? WebsafeSvg.network(
map['icon'],
width: 18,
height: 18,
color: Colors.black,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator()),
)
: Image.network(map['icon'])))
Lately somewhen this exception is in console
======== Exception caught by image resource service =====================
The following HttpException was thrown resolving an image codec:
, uri = https://www.xxx.sk/images/svgs/culture.png
When the exception was thrown, this was the stack:
Image provider: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
Image key: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
I dont know, what all to send, so far at least this. Thanks.

Flutter - how to add animation to a Widget so that it slides into the view when a button is pressed?

Let's make a simple example, given a Column(), I have 2 Containers in it, and a button.
Column(
children: [
MyButton(
label: "Expand me"
onTap: () => setState(() => isOpen = !isOpen)
),
Container(
child: Text("Container 1"),
height: 200
),
if (isOpen)
Container(
child: Text("Container 2"),
height: 150
)
]
)
so basically, if we press the button, the second Container will appear right under the first one, like an expansion panel.
Now I want to add an animation, and I'm having a hard time finding the best fit for my use case, as most solutions look really complex for such a simple task.
The animation is really simple, instead of making the Container 2 appear out of nowhere under the first one, it would be nice if the Container 2 would start behind Container 1, and then slide towards the bottom, until in position.
What is the cleanest way to achieve this in flutter?
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class EasyAnimatedOffset extends StatefulWidget {
EasyAnimatedOffset();
#override
_EasyAnimatedOffsetState createState() => _EasyAnimatedOffsetState();
}
class _EasyAnimatedOffsetState extends State<EasyAnimatedOffset>
with SingleTickerProviderStateMixin {
//Notice the "SingleTickerProviderStateMixin" above
//Must add "AnimationController"
late AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
//change the animation duration for a slower or faster animation.
//For example, replacing 1000 with 5000 will give you a 5x slower animation.
duration: Duration(milliseconds: 1000),
);
}
animateForward() {
_animationController.forward();
//this controller will move the animation forward
//you can also create a reverse animation using "_animationController.reverse()"
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//the offset has a x value and y value.
//changing the y axis value moves the animation vertically
//changing the x axis value moves the animation horizantaly
double xAxisValue = 0;
double yAxisValue = 10;
return AnimatedBuilder(
animation: _animationController,
// child: widget.child,
builder: (context, child) {
return Transform.translate(
offset: Offset(_animationController.value * xAxisValue,
_animationController.value * yAxisValue),
//add your button or widget here
child: InkWell(
onTap: () {
animateForward();
},
child: Center(
child: Container(
height: 100,
width: 200,
color: Colors.amber,
child: Center(
child: Text(
"Animate Me",
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
)),
)));
});
}
}

Floor plan in flutter

i'm trying to come up with best way to draw a floor plan in flutter, something like these images, but it would be for regals in one concrete shop, instead of plan of shopping centre with multiple shops.
floor plan 1
floor plan 2
i decided rectangles would be sufficient enough for now and i have multiple ideas on how to execute, but no idea which one is the best. or maybe there is even better one i have not thought of
1. using custom painter
regals have attributes: ax, ay, bx, by, so they go from point a (left bottom) to b (right upper)
code like this
final rect = Rect.fromPoints(
Offset(regal.ax.toDouble(), size.height - regal.ay.toDouble()),
Offset(regal.bx.toDouble(), size.height - regal.by.toDouble()),
);
this is good because it is flexible, there is pretty much unlimited range of options, but using CustomPainter is a bit buggy in my case, alongside with Transform and GestureDetector it bugs out sometimes and instead of clicking on "buttons" you need to track where user clicked, ehm, tapped.
2. using gridView?
i dont have thought this thru as much as first option, but big plus would be using styled buttons as regals, instead of rectangles.
possible problems would be button sizing, if one regal would be times bigger than others.
regal attributes would be order on x axis, order on y axis, x flex (for example 3 as 3 times of base size), y flex
i think i have not thought of the best solution yet.
what would it be?
Here is a quick playground using a Stack of Regals who are just Containers in this quick implementation under 250 lines of code.
Click the FloatActionButton to create random Regal. Then, you can define the position of each Regal and its Size, within the limit of the Floor Plan and Max/min Regal Size.
In this quick implementation, the position of a Regal can be defined both with Gestures or Sliders; while its size can only be defined using the sliders.
Package Dependencies
Riverpod (Flutter Hooks flavor) for State Management
Freezed for Domain classes immutability
Full Source Code (222 lines)
import 'dart:math' show Random;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part '66478145.floor_plan.freezed.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
),
);
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final regals = useProvider(regalsProvider.state);
return Scaffold(
body: Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Stack(
children: [
Container(
width: kFloorSize.width,
height: kFloorSize.height,
color: Colors.amber.shade100),
...regals
.map(
(regal) => Positioned(
top: regal.offset.dy,
left: regal.offset.dx,
child: GestureDetector(
child: RegalWidget(regal: regal),
),
),
)
.toList(),
],
),
const SizedBox(width: 16.0),
RegalProperties(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read(regalsProvider).createRegal(),
child: Icon(Icons.add),
),
);
}
}
class RegalWidget extends HookWidget {
final Regal regal;
const RegalWidget({Key key, this.regal}) : super(key: key);
#override
Widget build(BuildContext context) {
final _previousOffset = useState<Offset>(null);
final _refOffset = useState<Offset>(null);
return GestureDetector(
onTap: () => context.read(selectedRegalIdProvider).state = regal.id,
onPanStart: (details) {
_previousOffset.value = regal.offset;
_refOffset.value = details.localPosition;
},
onPanUpdate: (details) => context.read(regalsProvider).updateRegal(
regal.copyWith(
offset: _previousOffset.value +
details.localPosition -
_refOffset.value),
),
child: Container(
width: regal.size.width,
height: regal.size.height,
color: regal.color,
),
);
}
}
class RegalProperties extends HookWidget {
#override
Widget build(BuildContext context) {
final regal = useProvider(selectedRegalProvider);
return Padding(
padding: EdgeInsets.all(16.0),
child: regal == null
? Text('Click a Regal to start')
: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('WIDTH'),
Slider(
min: kRegalMinSize.width,
max: kRegalMaxSize.width,
value: regal.size.width,
onChanged: (value) => context
.read(regalsProvider)
.updateRegal(
regal.copyWith(size: Size(value, regal.size.height)),
),
),
const SizedBox(height: 16.0),
Text('HEIGHT'),
Slider(
min: kRegalMinSize.height,
max: kRegalMaxSize.height,
value: regal.size.height,
onChanged: (value) => context
.read(regalsProvider)
.updateRegal(
regal.copyWith(size: Size(regal.size.width, value)),
),
),
const SizedBox(height: 16.0),
Text('LEFT'),
Slider(
min: 0,
max: kFloorSize.width - regal.size.width,
value: regal.offset.dx,
onChanged: (value) =>
context.read(regalsProvider).updateRegal(
regal.copyWith(
offset: Offset(value, regal.offset.dy)),
),
),
const SizedBox(height: 16.0),
Text('TOP'),
Slider(
min: 0,
max: kFloorSize.height - regal.size.height,
value: regal.offset.dy,
onChanged: (value) =>
context.read(regalsProvider).updateRegal(
regal.copyWith(
offset: Offset(regal.offset.dx, value)),
),
),
],
),
),
);
}
}
final selectedRegalIdProvider = StateProvider<String>((ref) => null);
final selectedRegalProvider = Provider<Regal>((ref) {
final selectedId = ref.watch(selectedRegalIdProvider).state;
final regals = ref.watch(regalsProvider.state);
return regals.firstWhereOrNull((regal) => regal.id == selectedId);
});
final regalsProvider =
StateNotifierProvider<RegalsNotifier>((ref) => RegalsNotifier());
class RegalsNotifier extends StateNotifier<List<Regal>> {
final Size floorSize;
final Size maxSize;
RegalsNotifier({
this.floorSize = const Size(600, 400),
this.maxSize = const Size(100, 100),
List<Regal> state,
}) : super(state ?? []);
void createRegal() {
state = [...state, Regal.random];
print(state.last);
}
void updateRegal(Regal updated) {
state = state.map((r) => r.id == updated.id ? updated : r).toList();
}
}
#freezed
abstract class Regal implements _$Regal {
const factory Regal({
String id,
Color color,
Offset offset,
Size size,
}) = _Regal;
static Regal get random {
final rnd = Random();
return Regal(
id: DateTime.now().millisecondsSinceEpoch.toString(),
color: Color(0xff555555 + rnd.nextInt(0x777777)),
offset: Offset(
rnd.nextDouble() * (kFloorSize.width - kRegalMaxSize.width),
rnd.nextDouble() * (kFloorSize.height - kRegalMaxSize.height),
),
size: Size(
kRegalMinSize.width +
rnd.nextDouble() * (kRegalMaxSize.width - kRegalMinSize.width),
kRegalMinSize.height +
rnd.nextDouble() * (kRegalMaxSize.height - kRegalMinSize.height),
),
);
}
}
// CONFIG
const kFloorSize = Size(600, 400);
const kRegalMinSize = Size(10, 10);
const kRegalMaxSize = Size(200, 200);

Flutter: Drag Draggable Stack item inside a Draggable Stack Item

I have a Draggable on a DragTarget as part of a Stack. Inside is another Stack with Draggables, again on DragTargets and so on... (Stack over Stack over Stack etc.).
The Draggable is a Positioned with a Listener telling where to be placed.
homeView.dart
body: Stack(children: [
DraggableWidget(parentKey, Offset(0, 0)),
]),
draggableWidget.dart
class DraggableWidget extends StatefulWidget {
final Key itemKey;
final Offset itemPosition;
DraggableWidget(this.itemKey, this.itemPosition);
#override
_DraggableWidgetState createState() => _DraggableWidgetState();
}
class _DraggableWidgetState extends State<DraggableWidget> {
Offset tempDelta = Offset(0, 0);
Window<List<Key>> item;
List<DraggableWidget> childList = [];
Map<Key, Window<List>> structureMap;
initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
structureMap = Provider.of<Data>(context).structureMap;
if (structureMap[widget.itemKey] != null) {
structureMap[widget.itemKey].childKeys.forEach(
(k) => childList.add(
DraggableWidget(k, item.position),
),
);
} else {
structureMap[widget.itemKey] = Window<List<Key>>(
title: 'App',
key: widget.itemKey,
size: Size(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height),
position: Offset(0, 0),
color: Colors.blue,
childKeys: []);
}
item = Provider.of<Data>(context).structureMap[widget.itemKey];
return Positioned(
top: item.position.dx,
left: item.position.dy,
child: DragTarget(
builder:
(buildContext, List<Window<List<Key>>> candidateData, rejectData) {
return Listener(
onPointerDown: (PointerDownEvent event) {},
onPointerUp: (PointerUpEvent event) {
setState(() {
item.position = Offset(item.position.dx + tempDelta.dx,
item.position.dy + tempDelta.dy);
tempDelta = Offset(0, 0);
});
},
onPointerMove: (PointerMoveEvent event) {
tempDelta = Offset((event.delta.dy + tempDelta.dx),
(event.delta.dx + tempDelta.dy));
},
child: Draggable(
childWhenDragging: Container(),
feedback: Container(
color: item.color,
height: item.size.height,
width: item.size.width,
),
child: Column(children: [
Text(item.title),
Container(
color: item.color,
height: item.size.height,
width: item.size.width,
child: ItemStackBuilder(widget.itemKey, item.position),
),
]),
data: item),
);
},
),
);
}
}
itemStackBuilder.dart
class ItemStackBuilder extends StatelessWidget {
final Key itemKey;
final Offset itemPosition;
ItemStackBuilder(this.itemKey, this.itemPosition);
#override
Widget build(BuildContext context) {
Map<Key, Window<List<Key>>> structureMap =
Provider.of<Data>(context).structureMap;
if (structureMap[itemKey] == null) {
structureMap[itemKey] = Window(size: Size(20, 20), childKeys: []);
}
return Stack(overflow: Overflow.visible, children: [
...stackItems(context),
Container(
height: structureMap[itemKey].size.height,
width: structureMap[itemKey].size.width,
color: Colors.transparent),
]);
}
List<Widget> stackItems(BuildContext context) {
List<Key> childKeyList =
Provider.of<Data>(context).structureMap[itemKey].childKeys;
var stackItemDraggable;
List<Widget> stackItemsList = [];
if (childKeyList == null || childKeyList.length < 1) {
stackItemsList = [Container()];
} else {
for (int i = 0; i < childKeyList.length; i++) {
stackItemDraggable = DraggableWidget(childKeyList[i], itemPosition);
stackItemsList.add(stackItemDraggable);
}
}
return stackItemsList;
}
}
When I want to move the Draggable item on top, the underlying Stack moves.
I tried it with a Listener widget and was able to detect all RenderBoxes inside the Stack.
But how can I select the specific Draggable and/or disable all the other layers? Is it a better idea to forget about Draggables and do it all with Positioned and GestureDetector?
Ok, it was my mistake not of the framework:
on itemStackBuilder.dart I used an additional Container to size the Stack. I was not able to recognise, because color was transparent:
Container(
height: structureMap[itemKey].size.height,
width: structureMap[itemKey].size.width,
color: Colors.transparent),
]);
}
After deleting this part, all works fine for now.

Why do i get a RangeError, if i add something to my List?

im trying to create a new Hero Widget by klicking on my FloatingActionButton. Therefore i have created a HeroCover widget, which holds the single Hero widgets.
class HeroCover extends StatelessWidget {
final Widget callPage;
final heroTag;
final coverImageName;
final name;
HeroCover({this.callPage, this.heroTag, this.coverImageName, this.name});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Hero(
tag: heroTag,
child: GestureDetector(
onTap: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => callPage)),
child: Column(children: <Widget>[
Image(
image: new AssetImage(coverImageName),
height: 100,
width: 100,
),
Text(name),
])),
),
);
}
}
On my HeroPage i now Create those HeroCover widgets depending on the following Lists with mapping
static List<int> matrixID = [0, 1, 2];
static var heroTag = ['matrix1', 'matrix2', 'matrix3'];
static var name = ['Matrix Kitchen', 'DAAANCEFLOR', 'Bath'];
static var matrixIMG = [
'imgs/matrix1.png',
'imgs/matrix2.png',
'imgs/matrix3.png'
];
var matrixCall = [
...matrixID.map((id) {
return MatrixPageOne(
name: name[id],
matrixSize: 20,
heroTag: heroTag[id],
heroImage: matrixIMG[id],
);
}).toList(),
];
Here i map the matrixID in the BuildMethod to return HeroCover Widgets depending on matrixID's length:
body: Column(
children: [
Wrap(children: [
...matrixID.map((id) {
return HeroCover(
heroTag: heroTag[id],
callPage: matrixCall[id],
name: name[id],
coverImageName: matrixIMG[id],
);
}).toList()
] // wrap children
),
],
),
Now if i press my FloatingActionButton, i add one Element to each of the lists:
floatingActionButton: FloatingActionButton(
onPressed: () {
//startAddMatrix(context);
setState(() {
matrixID.add(matrixID.length);
name.add('new Matrix');
matrixIMG.add('imgs/matrix1.png');
heroTag.add(DateTime.now().toString());
});
},
child: Icon(Icons.add),
backgroundColor: color_3,
),
So the .map should find one more element in each list and the next HeroCover Widget should be displayed ( if i add it manually to each list there is no problem), but if i press my FloatingActionButton, this happens:
but if i tap on "Home" in my BottomNavigationBar now and back to "Devices" everything is as it should be:
i just dont understand why .add is causing an RangeError. If anyone knows whats wrong here, id be very Thankful for your help!
your matrixCall init with ...matrixID.map((id) { ,
so it have 3 values 0..2
In your floatingActionButton, did not extend matrixCall, matrixCall still only have 3 values 0..2
when use
Wrap(children: [
...matrixID.map((id) {
return HeroCover(
heroTag: heroTag[id],
callPage: matrixCall[id],
name: name[id],
coverImageName: matrixIMG[id],
);
}).toList()
matrixID have 4 values 0..3,
and matrixCall still have 3 values, matrixCall[3] do not have value.