Skip to content

Instantly share code, notes, and snippets.

@vindolin
Last active February 6, 2023 08:57
Show Gist options
  • Save vindolin/6e89bca02d66954cd299858d54191559 to your computer and use it in GitHub Desktop.
Save vindolin/6e89bca02d66954cd299858d54191559 to your computer and use it in GitHub Desktop.
Animated CustomPainter + Riverpod

Animated CustomPainter + Riverpod

Created with <3 with dartpad.dev.

// 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