| // 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<VeryLongPictureScrollingPerf> { |
| bool consolidate = false; |
| bool useList = false; |
| Int16List waveData = loadGraph(); |
| |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| appBar: AppBar( |
| actions: <Widget>[ |
| Row( |
| children: <Widget>[ |
| const Text('list:'), |
| Checkbox(value: useList, onChanged: (bool? value) => setState(() { |
| useList = value!; |
| }),), |
| ], |
| ), |
| Row( |
| children: <Widget>[ |
| 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<String>('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<String>('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; |
| } |