| // 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; |
| } |
| int r = 0; |
| int g = 0; |
| int 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 double cx = x; |
| final double cy = y + h/2; |
| final Offset p0 = Offset(cx + s, cy + c); |
| final Offset 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 Paint p = Paint(); |
| p.color = color(baseFactor); |
| canvas.drawRect(Offset.zero & size, p); |
| for (int j = 0; j < nDown; j++) { |
| for (int 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 Paint p = Paint(); |
| p.color = color(baseFactor); |
| canvas.drawRect(Offset.zero & size, p); |
| for (int j = 0; j < nDown; j++) { |
| for (int 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 Paint p = Paint(); |
| p.color = color(baseFactor); |
| canvas.drawRect(Offset.zero & size, p); |
| for (int j = 0; j < nDown; j++) { |
| for (int 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; |
| } |