Created
October 19, 2023 09:53
-
-
Save sirpengi/df56d54f40166643fca94342ab8937fd to your computer and use it in GitHub Desktop.
Implicit animation states in flutter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Test project with a ListView.builder where the items have an | |
// implicit animation widget (this case an AnimatedContainer, in zulip-flutter | |
// for unread-markers we're using AnimatedOpacity) and the length | |
// of items sent into the ListView are changing. This simulates in ZF | |
// where we have start and end markers that are dynamically added/removed. | |
// The addition of a 'mark as read' marker at the beginning of the list | |
// cause most animations to lose their state (and thus no longer animated, but | |
// immediately transitioned to their new state). | |
// | |
// This test project solves that by giving the items a unique key and | |
// implementing `findChildIndexCallback` in `ListView.builder`. | |
// See https://api.flutter.dev/flutter/widgets/SliverChildBuilderDelegate/findChildIndexCallback.html | |
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Test', | |
theme: ThemeData( | |
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), | |
useMaterial3: true, | |
), | |
home: const MyHomePage(title: 'Scratch'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
const MyHomePage({super.key, required this.title}); | |
final String title; | |
@override | |
State<MyHomePage> createState() => _MyHomePageState(); | |
} | |
enum DisplayItemType { | |
on, off, extra; | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
final List<bool> _fakeState = List.filled(10, false); | |
final List<(int, DisplayItemType)> _displayItems = []; | |
bool _appended = false; // tracks if the extra item should be added to the list | |
void _randomizeFlags() { | |
setState(() { | |
// randomize all flags | |
final r = Random(); | |
for(var i = 0; i < _fakeState.length; i++) { | |
_fakeState[i] = r.nextBool(); | |
} | |
// create a _displayItems list based on _fakeState | |
// but with an extra item on every other click | |
_displayItems.clear(); | |
for(var i = 0; i < _fakeState.length; i++) { | |
_displayItems.add((i, _fakeState[i] ? DisplayItemType.on : DisplayItemType.off)); | |
} | |
_appended = !_appended; | |
if (_appended) { | |
_displayItems.insert(0, (-1, DisplayItemType.extra)); | |
} | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
backgroundColor: Theme.of(context).colorScheme.inversePrimary, | |
title: Text(widget.title), | |
), | |
body: ListView.builder( | |
itemCount: _displayItems.length, | |
findChildIndexCallback: (Key key) { | |
// Index is shifted by 1 if there is an extra "appended" item. | |
// We didn't give the "extra" item a key but that seems to | |
// be fine (and the callback isn't called for it). | |
final valueKey = key as ValueKey; | |
if (_appended) { | |
return valueKey.value + 1; | |
} else { | |
return valueKey.value; | |
} | |
}, | |
itemBuilder: (BuildContext context, int i) { | |
final (key, flag) = _displayItems[i]; | |
switch (flag) { | |
case DisplayItemType.extra: | |
return const SizedBox(height: 40, child: Text('extra')); | |
case DisplayItemType.on: | |
case DisplayItemType.off: | |
final color = (flag == DisplayItemType.on) ? Colors.green : Colors.red; | |
return SizedBox( | |
key: ValueKey(key), | |
height: 40, | |
child: AnimatedContainer( | |
duration: const Duration(seconds: 2), | |
color: color, | |
child: Text('$key $flag'))); | |
} | |
}, | |
), | |
floatingActionButton: FloatingActionButton( | |
onPressed: _randomizeFlags, | |
tooltip: 'Randomize', | |
child: const Icon(Icons.question_mark), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment