Created with <3 with dartpad.dev.
Last active
February 6, 2023 08:57
-
-
Save vindolin/6e89bca02d66954cd299858d54191559 to your computer and use it in GitHub Desktop.
Animated CustomPainter + Riverpod
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
// ignore_for_file: avoid_print | |
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_hooks/flutter_hooks.dart'; | |
import 'package:hooks_riverpod/hooks_riverpod.dart'; | |
void main() { | |
runApp( | |
const ProviderScope( | |
child: MyApp(), | |
), | |
); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
// This widget is the root of your application. | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Riverpod + animated CustomPainter', | |
home: const Example2(), | |
theme: ThemeData( | |
colorScheme: ColorScheme.fromSeed( | |
seedColor: Colors.orange, | |
brightness: MediaQueryData.fromWindow(WidgetsBinding.instance.window).platformBrightness, | |
surface: Colors.deepOrange[600], | |
), | |
), | |
); | |
} | |
} | |
class ItemPainter extends CustomPainter { | |
final double value; | |
ItemPainter(this.value); | |
final itemPaint = Paint()..color = Colors.orange; | |
@override | |
void paint(Canvas canvas, Size size) { | |
// draw a circle with a size depending on the value | |
double radius = size.width / 10 * value / 2; | |
canvas.drawCircle( | |
Offset( | |
size.width / 2, | |
size.height / 2, | |
), | |
radius, | |
itemPaint, | |
); | |
} | |
@override | |
bool shouldRepaint(covariant ItemPainter oldDelegate) => oldDelegate.value != value; | |
} | |
CustomPaint itemIcon(double value) { | |
return CustomPaint( | |
painter: ItemPainter(value), | |
size: const Size(40, 40), | |
); | |
} | |
@immutable | |
class Item { | |
const Item({required this.id, required this.value}); | |
final String id; | |
final double value; | |
} | |
// notifier that provides a list of items | |
class ItemsNotifier extends Notifier<List<Item>> { | |
@override | |
List<Item> build() { | |
return [ | |
const Item(id: 'A', value: 1.0), | |
const Item(id: 'B', value: 5.0), | |
const Item(id: 'C', value: 10.0), | |
]; | |
} | |
void randomize(String id) { | |
// replace the state with a new list of items where the value is randomized from 0.0 to 10.0 | |
state = [ | |
for (final item in state) | |
if (item.id == id) Item(id: item.id, value: Random().nextInt(100).toDouble() / 10.0) else item, | |
]; | |
} | |
} | |
class AnimatedItem extends HookWidget { | |
final Item item; | |
final Duration duration = const Duration(milliseconds: 500); | |
const AnimatedItem(this.item, {super.key}); | |
@override | |
Widget build(BuildContext context) { | |
final animationController = useAnimationController( | |
initialValue: item.value, | |
duration: duration, | |
lowerBound: 0.0, | |
upperBound: 10.0, | |
); | |
useEffect(() { | |
if (item.value != animationController.value) { | |
animationController.animateTo(item.value); | |
} | |
return null; | |
}, [item.value]); | |
return AnimatedBuilder( | |
animation: animationController, | |
builder: (context, child) { | |
return itemIcon(animationController.value); | |
}, | |
); | |
} | |
} | |
final itemsProvider = NotifierProvider<ItemsNotifier, List<Item>>(() => ItemsNotifier()); | |
class Example2 extends HookConsumerWidget { | |
const Example2({super.key}); | |
@override | |
Widget build(BuildContext context, WidgetRef ref) { | |
final items = ref.watch(itemsProvider); | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Animated CustomPainter'), | |
), | |
// iterate over the item list in ItemsNotifier | |
body: ListView.separated( | |
separatorBuilder: (context, index) => const Divider(), | |
itemCount: items.length, | |
itemBuilder: (context, index) { | |
final item = items.elementAt(index); | |
return ListTile( | |
key: Key(item.id), | |
// leading: AnimatedItem(item), | |
leading: AnimatedItem(item), | |
title: Text('${item.value}'), | |
); | |
}, | |
), | |
floatingActionButton: Row( | |
mainAxisAlignment: MainAxisAlignment.end, | |
children: [ | |
FloatingActionButton( | |
onPressed: () { | |
ref.read(itemsProvider.notifier).randomize( | |
['A', 'B', 'C'][(Random()).nextInt(3)], | |
); // randomize the size of one of the items | |
}, | |
child: const Icon(Icons.change_circle), | |
), | |
], | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment