| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:math'; | |
| import 'dart:ui' as ui; | |
| import 'package:flutter/material.dart'; | |
| import '../common.dart'; | |
| Map<String, WidgetBuilder> gradientPerfRoutes = <String, WidgetBuilder>{ | |
| kGradientPerfRecreateDynamicRouteName: (BuildContext _) => const RecreateDynamicPainterPage(), | |
| kGradientPerfRecreateConsistentRouteName: (BuildContext _) => | |
| const RecreateConsistentPainterPage(), | |
| kGradientPerfStaticConsistentRouteName: (BuildContext _) => const StaticConsistentPainterPage(), | |
| }; | |
| typedef CustomPaintFactory = CustomPainter Function(double hue); | |
| class GradientPerfHomePage extends StatelessWidget { | |
| const GradientPerfHomePage({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar(title: const Text('Gradient Perf')), | |
| body: ListView( | |
| key: const Key(kGradientPerfScrollableName), | |
| children: <Widget>[ | |
| ElevatedButton( | |
| key: const Key(kGradientPerfRecreateDynamicRouteName), | |
| child: const Text('Recreate Dynamic Gradients'), | |
| onPressed: () { | |
| Navigator.pushNamed(context, kGradientPerfRecreateDynamicRouteName); | |
| }, | |
| ), | |
| ElevatedButton( | |
| key: const Key(kGradientPerfRecreateConsistentRouteName), | |
| child: const Text('Recreate Same Gradients'), | |
| onPressed: () { | |
| Navigator.pushNamed(context, kGradientPerfRecreateConsistentRouteName); | |
| }, | |
| ), | |
| ElevatedButton( | |
| key: const Key(kGradientPerfStaticConsistentRouteName), | |
| child: const Text('Static Gradients'), | |
| onPressed: () { | |
| Navigator.pushNamed(context, kGradientPerfStaticConsistentRouteName); | |
| }, | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class _PainterPage extends StatefulWidget { | |
| const _PainterPage({super.key, required this.title, required this.factory}); | |
| final String title; | |
| final CustomPaintFactory factory; | |
| @override | |
| State<_PainterPage> createState() => _PainterPageState(); | |
| } | |
| class RecreateDynamicPainterPage extends _PainterPage { | |
| const RecreateDynamicPainterPage({super.key}) | |
| : super(title: 'Recreate Dynamic Gradients', factory: makePainter); | |
| static CustomPainter makePainter(double f) { | |
| return RecreatedDynamicGradients(baseFactor: f); | |
| } | |
| } | |
| class RecreateConsistentPainterPage extends _PainterPage { | |
| const RecreateConsistentPainterPage({super.key}) | |
| : super(title: 'Recreate Same Gradients', factory: makePainter); | |
| static CustomPainter makePainter(double f) { | |
| return RecreatedConsistentGradients(baseFactor: f); | |
| } | |
| } | |
| class StaticConsistentPainterPage extends _PainterPage { | |
| const StaticConsistentPainterPage({super.key}) | |
| : super(title: 'Reuse Same Gradients', factory: makePainter); | |
| static CustomPainter makePainter(double f) { | |
| return StaticConsistentGradients(baseFactor: f); | |
| } | |
| } | |
| class _PainterPageState extends State<_PainterPage> with SingleTickerProviderStateMixin { | |
| late AnimationController _controller; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _controller = AnimationController(vsync: this); | |
| _controller.repeat(period: const Duration(seconds: 2)); | |
| } | |
| @override | |
| void dispose() { | |
| _controller.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar(title: Text(widget.title)), | |
| body: Center( | |
| child: AnimatedBuilder( | |
| animation: _controller, | |
| builder: (BuildContext context, Widget? child) { | |
| return CustomPaint( | |
| size: const Size(paintW, paintH), | |
| painter: widget.factory(_controller.value), | |
| willChange: true, | |
| ); | |
| }, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| Color color(double factor) { | |
| int v = ((factor * 255 * 3) % (255 * 3)).round(); | |
| if (v < 0) { | |
| v += 255 * 3; | |
| } | |
| var r = 0; | |
| var g = 0; | |
| var b = 0; | |
| if (v < 255) { | |
| r = 255 - v; | |
| g = v; | |
| } else { | |
| v -= 255; | |
| if (v < 255) { | |
| g = 255 - v; | |
| b = v; | |
| } else { | |
| v -= 255; | |
| b = 255 - v; | |
| r = v; | |
| } | |
| } | |
| return Color.fromARGB(255, r, g, b); | |
| } | |
| Shader rotatingGradient(double factor, double x, double y, double h) { | |
| final double s = sin(factor * 2 * pi) * h / 8; | |
| final double c = cos(factor * 2 * pi) * h / 8; | |
| final cx = x; | |
| final double cy = y + h / 2; | |
| final p0 = Offset(cx + s, cy + c); | |
| final p1 = Offset(cx - s, cy - c); | |
| return ui.Gradient.linear(p0, p1, <Color>[color(factor), color(factor + 0.5)]); | |
| } | |
| const int nAcross = 12; | |
| const int nDown = 16; | |
| const double cellW = 20; | |
| const double cellH = 20; | |
| const double hGap = 5; | |
| const double vGap = 5; | |
| const double paintW = hGap + (cellW + hGap) * nAcross; | |
| const double paintH = vGap + (cellH + vGap) * nDown; | |
| double x(int i, int j) { | |
| return hGap + i * (cellW + hGap); | |
| } | |
| double y(int i, int j) { | |
| return vGap + j * (cellH + vGap); | |
| } | |
| Shader gradient(double baseFactor, int i, int j) { | |
| final double lineFactor = baseFactor + 1 / 3 + 0.5 * (j + 1) / (nDown + 1); | |
| final double cellFactor = lineFactor + 1 / 3 * (i + 1) / (nAcross + 1); | |
| return rotatingGradient(cellFactor, x(i, j) + cellW / 2, y(i, j), cellH); | |
| } | |
| class RecreatedDynamicGradients extends CustomPainter { | |
| RecreatedDynamicGradients({required this.baseFactor}); | |
| final double baseFactor; | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| final p = Paint(); | |
| p.color = color(baseFactor); | |
| canvas.drawRect(Offset.zero & size, p); | |
| for (var j = 0; j < nDown; j++) { | |
| for (var i = 0; i < nAcross; i++) { | |
| p.shader = gradient(baseFactor, i, j); | |
| canvas.drawRect(Rect.fromLTWH(x(i, j), y(i, j), cellW, cellH), p); | |
| } | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(CustomPainter oldDelegate) => true; | |
| } | |
| class RecreatedConsistentGradients extends CustomPainter { | |
| RecreatedConsistentGradients({required this.baseFactor}); | |
| final double baseFactor; | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| final p = Paint(); | |
| p.color = color(baseFactor); | |
| canvas.drawRect(Offset.zero & size, p); | |
| for (var j = 0; j < nDown; j++) { | |
| for (var i = 0; i < nAcross; i++) { | |
| p.shader = gradient(0, i, j); | |
| canvas.drawRect(Rect.fromLTWH(x(i, j), y(i, j), cellW, cellH), p); | |
| } | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(CustomPainter oldDelegate) => true; | |
| } | |
| class StaticConsistentGradients extends CustomPainter { | |
| StaticConsistentGradients({required this.baseFactor}); | |
| final double baseFactor; | |
| static List<List<Shader>> gradients = <List<Shader>>[ | |
| for (int j = 0; j < nDown; j++) <Shader>[for (int i = 0; i < nAcross; i++) gradient(0, i, j)], | |
| ]; | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| final p = Paint(); | |
| p.color = color(baseFactor); | |
| canvas.drawRect(Offset.zero & size, p); | |
| for (var j = 0; j < nDown; j++) { | |
| for (var i = 0; i < nAcross; i++) { | |
| p.shader = gradients[j][i]; | |
| canvas.drawRect(Rect.fromLTWH(x(i, j), y(i, j), cellW, cellH), p); | |
| } | |
| } | |
| } | |
| @override | |
| bool shouldRepaint(CustomPainter oldDelegate) => true; | |
| } |