// 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:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; // Adapted from test case submitted in // https://github.com/flutter/flutter/issues/92366 // Converted to use fixed data rather than reading a waveform file class VeryLongPictureScrollingPerf extends StatefulWidget { const VeryLongPictureScrollingPerf({super.key}); @override State createState() => VeryLongPictureScrollingPerfState(); } class VeryLongPictureScrollingPerfState extends State { bool consolidate = false; bool useList = false; Int16List waveData = loadGraph(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ Row( children: [ const Text('list:'), Checkbox( value: useList, onChanged: (bool? value) => setState(() { useList = value!; }), ), ], ), Row( children: [ const Text('consolidate:'), Checkbox( value: consolidate, onChanged: (bool? value) => setState(() { consolidate = value!; }), ), ], ), ], ), backgroundColor: Colors.transparent, body: SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: useList ? ListView.builder( key: const ValueKey('vlp_list_view_scrollable'), scrollDirection: Axis.horizontal, clipBehavior: Clip.none, itemCount: (waveData.length / 200).ceil(), itemExtent: 100, itemBuilder: (BuildContext context, int index) => CustomPaint( painter: PaintSomeTest( waveData: waveData, from: index * 200, to: min((index + 1) * 200, waveData.length - 1), ), ), ) : SingleChildScrollView( key: const ValueKey('vlp_single_child_scrollable'), scrollDirection: Axis.horizontal, child: SizedBox( width: MediaQuery.of(context).size.width * 20, height: MediaQuery.of(context).size.height, child: RepaintBoundary( child: CustomPaint( isComplex: true, painter: PaintTest(consolidate: consolidate, waveData: waveData), ), ), ), ), ), ); } } class PaintTest extends CustomPainter { const PaintTest({required this.consolidate, required this.waveData}); final bool consolidate; final Int16List waveData; @override void paint(Canvas canvas, Size size) { final double halfHeight = size.height / 2; double x = 0; const double strokeSize = .5; const double zoomFactor = .5; final Paint paintPos = Paint() ..color = Colors.pink ..strokeWidth = strokeSize ..isAntiAlias = false ..style = PaintingStyle.stroke; final Paint paintNeg = Paint() ..color = Colors.pink ..strokeWidth = strokeSize ..isAntiAlias = false ..style = PaintingStyle.stroke; final Paint paintZero = Paint() ..color = Colors.green ..strokeWidth = strokeSize ..isAntiAlias = false ..style = PaintingStyle.stroke; int index = 0; Paint? listPaint; final Float32List offsets = Float32List(consolidate ? waveData.length * 4 : 4); int used = 0; for (index = 0; index < waveData.length; index++) { final (Paint curPaint, Offset p1) = switch (waveData[index]) { < 0 => (paintPos, Offset(x, halfHeight * (1 - waveData[index] / 32768))), > 0 => (paintNeg, Offset(x, halfHeight * (1 - waveData[index] / 32767))), _ => (paintZero, Offset(x, halfHeight + 1)), }; final Offset p0 = Offset(x, halfHeight); if (consolidate) { if (listPaint != null && listPaint != curPaint) { canvas.drawRawPoints(PointMode.lines, offsets.sublist(0, used), listPaint); used = 0; } listPaint = curPaint; offsets[used++] = p0.dx; offsets[used++] = p0.dy; offsets[used++] = p1.dx; offsets[used++] = p1.dy; } else { canvas.drawLine(p0, p1, curPaint); } x += zoomFactor; } if (consolidate && used > 0) { canvas.drawRawPoints(PointMode.lines, offsets.sublist(0, used), listPaint!); } } @override bool shouldRepaint(CustomPainter oldDelegate) { return oldDelegate is! PaintTest || oldDelegate.consolidate != consolidate || oldDelegate.waveData != waveData; } } class PaintSomeTest extends CustomPainter { const PaintSomeTest({required this.waveData, int? from, int? to}) : from = from ?? 0, to = to ?? waveData.length; final Int16List waveData; final int from; final int to; @override void paint(Canvas canvas, Size size) { final double halfHeight = size.height / 2; double x = 0; const double strokeSize = .5; const double zoomFactor = .5; final Paint paintPos = Paint() ..color = Colors.pink ..strokeWidth = strokeSize ..isAntiAlias = false ..style = PaintingStyle.stroke; final Paint paintNeg = Paint() ..color = Colors.pink ..strokeWidth = strokeSize ..isAntiAlias = false ..style = PaintingStyle.stroke; final Paint paintZero = Paint() ..color = Colors.green ..strokeWidth = strokeSize ..isAntiAlias = false ..style = PaintingStyle.stroke; for (int index = from; index <= to; index++) { final (Paint curPaint, Offset p1) = switch (waveData[index]) { < 0 => (paintPos, Offset(x, halfHeight * (1 - waveData[index] / 32768))), > 0 => (paintNeg, Offset(x, halfHeight * (1 - waveData[index] / 32767))), _ => (paintZero, Offset(x, halfHeight + 1)), }; final Offset p0 = Offset(x, halfHeight); canvas.drawLine(p0, p1, curPaint); x += zoomFactor; } } @override bool shouldRepaint(CustomPainter oldDelegate) { return oldDelegate is! PaintSomeTest || oldDelegate.waveData != waveData || oldDelegate.from != from || oldDelegate.to != to; } } Int16List loadGraph() { final Int16List waveData = Int16List(350000); final Random r = Random(0x42); for (int i = 0; i < waveData.length; i++) { waveData[i] = r.nextInt(32768) - 16384; } return waveData; }