| // 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 'package:flutter/material.dart'; | |
| /// Macrobenchmark page reproducing the text shadow scrolling workload from | |
| /// https://github.com/flutter/flutter/issues/190395. | |
| class TextShadowPerfPage extends StatefulWidget { | |
| const TextShadowPerfPage({super.key}); | |
| @override | |
| State<TextShadowPerfPage> createState() => _TextShadowPerfPageState(); | |
| } | |
| class _TextShadowPerfPageState extends State<TextShadowPerfPage> | |
| with SingleTickerProviderStateMixin { | |
| late final ScrollController _scrollController; | |
| late final AnimationController _animationController; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _scrollController = ScrollController(); | |
| _animationController = AnimationController(vsync: this, duration: const Duration(seconds: 10)) | |
| ..repeat(reverse: true); | |
| _animationController.addListener(_onAnimationTick); | |
| } | |
| void _onAnimationTick() { | |
| if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0) { | |
| _scrollController.jumpTo( | |
| _animationController.value * _scrollController.position.maxScrollExtent, | |
| ); | |
| } | |
| } | |
| @override | |
| void dispose() { | |
| _animationController.removeListener(_onAnimationTick); | |
| _animationController.dispose(); | |
| _scrollController.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| const columnCount = 7; | |
| const groupsPerColumn = 28; | |
| return Scaffold( | |
| backgroundColor: const Color(0xff080808), | |
| body: ScrollConfiguration( | |
| behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), | |
| child: SingleChildScrollView( | |
| controller: _scrollController, | |
| child: Row( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: List<Widget>.generate(columnCount, (int column) { | |
| return Expanded( | |
| child: Padding( | |
| padding: const EdgeInsets.all(6), | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.stretch, | |
| children: List<Widget>.generate(groupsPerColumn, (int row) { | |
| return _TextShadowGroup(index: column * groupsPerColumn + row); | |
| }), | |
| ), | |
| ), | |
| ); | |
| }), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class _TextShadowGroup extends StatelessWidget { | |
| const _TextShadowGroup({required this.index}); | |
| final int index; | |
| static const List<Shadow> _shadows = <Shadow>[Shadow(blurRadius: 1.0)]; | |
| @override | |
| Widget build(BuildContext context) { | |
| return SizedBox( | |
| height: 230, | |
| child: Align( | |
| alignment: Alignment.bottomLeft, | |
| child: Padding( | |
| padding: const EdgeInsets.all(12), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: <Widget>[ | |
| Text( | |
| 'Synthetic text item $index', | |
| maxLines: 2, | |
| overflow: TextOverflow.ellipsis, | |
| style: const TextStyle( | |
| color: Colors.white, | |
| fontSize: 10, | |
| fontWeight: FontWeight.bold, | |
| shadows: _shadows, | |
| ), | |
| ), | |
| Row( | |
| children: <Widget>[ | |
| Text( | |
| '${8 + index % 14}:30', | |
| style: const TextStyle( | |
| color: Color(0xff43ddff), | |
| fontSize: 9, | |
| fontWeight: FontWeight.bold, | |
| shadows: _shadows, | |
| ), | |
| ), | |
| const Spacer(), | |
| Text( | |
| 'Ep ${1 + index % 24}', | |
| style: const TextStyle(color: Colors.white70, fontSize: 9, shadows: _shadows), | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } |