I would like to, on long press on a item in the list, animate in a checkbox on the leading end of the list item for every list item like in this video.
I have tried with implicit animations, here under is my list item code.
class InspectionItemWidget extends StatefulWidget {
final Inspection inspection;
final Function(Inspection, bool) selected;
const InspectionItemWidget(
{Key? key, required this.inspection, required this.selected})
: super(key: key);
@override
State<InspectionItemWidget> createState() => _InspectionItemWidgetState();
}
class _InspectionItemWidgetState extends State<InspectionItemWidget> {
File? _imageFile;
final _duration = const Duration(milliseconds: 400);
late double _selectWidth;
late double _opacity;
late bool _selected;
@override
void initState() {
super.initState();
_selectWidth = 0.0;
_opacity = 0.0;
_selected = false;
di<EventBus>().on<InspectionSelectEvent>().listen((event) {
if (mounted) {
setState(() {
if (event.selectMode) {
_selectWidth = 60.0;
_opacity = 1.0;
} else {
_selectWidth = 0.0;
_opacity = 0.0;
}
});
}
});
}
@override
Widget build(BuildContext context) {
AppLocalizations loc = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 8),
child: SizedBox(
height: 80,
child: PhysicalModel(
color: Colors.white,
elevation: 8,
child: Row(
children: [
_buildSelectBox(),
_buildImage(),
Padding(
padding: const EdgeInsets.only(left: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(getStatusName(widget.inspection.status, loc),
style: TextStyle(color: colorMedium)),
Text(getTypeName(widget.inspection.type, loc),
style: const TextStyle(fontWeight: FontWeight.bold)),
Text(formatDateTime(widget.inspection.created)),
Text(widget.inspection.description)
],
),
)
],
),
),
),
);
}
Widget _buildSelectBox() {
return AnimatedContainer(
duration: _duration,
width: _selectWidth,
child: AnimatedOpacity(
opacity: _opacity,
duration: _duration,
child: Checkbox(
value: _selected,
onChanged: (_) {
setState(() {
_selected = !_selected;
});
widget.selected(widget.inspection, _selected);
}),
),
);
}
Widget _buildImage() {
if (widget.inspection.imageName.isNotEmpty) {
if (_imageFile != null) {
return SizedBox(
width: 80,
height: 80,
child: Padding(
padding: const EdgeInsets.all(4),
child: Image.file(_imageFile!,
width: 72, height: 72, fit: BoxFit.fill)),
);
} else {
return FutureBuilder<void>(
future: _loadImage(widget.inspection.imageName),
builder: (context, snapshot) {
return SizedBox(
width: 80,
height: 80,
child: Padding(
padding: const EdgeInsets.all(4),
child: snapshot.connectionState == ConnectionState.done
? Image.file(_imageFile!,
width: 72, height: 72, fit: BoxFit.fill)
: Container(),
),
);
});
}
}
return const SizedBox(
width: 80,
height: 80,
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(Icons.no_photography, size: 72),
));
}
Future<void> _loadImage(String name) async {
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory();
String appDocumentsPath = appDocumentsDirectory.path;
String filePath = '$appDocumentsPath/images/$name';
_imageFile = File(filePath);
}
}
But the animation is not happening?
Also, I am using a event bus to start the animation for every item, is there a better way for this? A way to signal every item to start the animation?
You can achieve this in multiple ways. Here's one simple solution using AnimatedCrossFade
.
For time being, I did it for just one item (other items won't show an unchecked checkbox, but you could do it easily with any state management solution of your choice).
class AnimationDemo extends StatefulWidget {
@override
AnimationDemoState createState() => AnimationDemoState();
}
class AnimationDemoState extends State<AnimationDemo> {
bool isAllSelected = false;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Item(
(index + 1).toString(),
isAllSelected: isAllSelected,
onLongPress: () {
setState(() {
isAllSelected = !isAllSelected;
});
},
);
},
itemCount: 20,
);
}
}
class Item extends StatefulWidget {
final String title;
final bool isAllSelected;
final VoidCallback onLongPress;
const Item(this.title,
{required this.isAllSelected, required this.onLongPress});
@override
ItemState createState() => ItemState();
}
class ItemState extends State<Item> with TickerProviderStateMixin {
bool isSelected = false;
//todo: instead of this, use a global variable with any state management solution to show unchecked boxes for others
@override
Widget build(BuildContext context) {
return ListTile(
onLongPress: () {
setState(() {
isSelected = !isSelected;
});
},
title: Text(widget.title),
minLeadingWidth: 0,
leading: AnimatedCrossFade(
alignment: Alignment.bottomLeft,
reverseDuration: const Duration(milliseconds: 200),
secondCurve: Curves.linearToEaseOut,
duration: const Duration(milliseconds: 200),
firstChild: Checkbox(value: true, onChanged: (_) {}),
secondChild: const SizedBox(),
crossFadeState:
isSelected ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),
);
}
}