Repository: MarcelGarus/marquee
Branch: master
Commit: 0f3061dae11b
Files: 12
Total size: 31.4 KB
Directory structure:
gitextract_wvwhnoz3/
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── lib/
│ │ └── main.dart
│ └── pubspec.yaml
├── lib/
│ └── marquee.dart
└── pubspec.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
.dart_tool/
.packages
.pub/
.idea/
.vscode/
*.lock
build/
android/
ios/.generated/
ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.*
================================================
FILE: .metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 5ab9e70727d858def3a586db7fb98ee580352957
channel: beta
================================================
FILE: CHANGELOG.md
================================================
## 2.3.0
- Update `fading_edge_scrollview` dependency to `4.1.1` to support flutter 3.24
- Addded @dispose call for controller to stop marquee from recreating with new controller - major performance gain.
- Thanks to @shilosaadon and @realitymolder
## 2.2.3
- Update `fading_edge_scrollview` dependency to `3.0.0`, which supports Flutter 3.
- Thanks to @jannisnikoy
## 2.2.2
- Update to Flutter 3.
- Simplify the readme.
- Strip down example to the essentials.
- Thanks to @ymback
## 2.2.1
- Fix error: ScrollController not attached to any scroll views.
- Thanks to @SergeyShustikov
## 2.2.0
- Add support for right to left languages.
- Thanks to @yanivshaked
## 2.1.0
- Add `onDone` property.
- Fix bug of using the wrong padding and `startPosition` when you delay scrolling.
- Thanks to @Jeferson505 and @emagnier
## 2.0.0
- Migrate to null-safety.
- Thanks to @Konrad97
## 1.7.0
- Add `textScaleFactor` parameter.
- Thanks to @Sprechen
## 1.6.1
- Fixed bug of detached `ScrollController` after `startAfter` duration.
- Improve this changelog.
- Thanks to @Sauceee and @nt4f04uNd
## 1.6.0
- Added `startAfter` argument to start scrolling after a duration.
- Make documentation better.
- Thanks to @hacker1024
## 1.5.3
- Removed fading on the web.
- Thanks to @HardVeur
## 1.5.2
- Fixed the fading flashing even if there is no pause.
## 1.5.1
- Minor fixes.
- Thanks to @arnaudenub
## 1.5.0
- Added feature faded edges.
- Thanks to @arnaudenub
## 1.4.0
- Added support to stop the scroll after a given number of rounds.
- Thanks to @arnaudenub
## 1.3.1
- Minor fixes.
- Thanks to @kodebot and @danieldai
## 1.2.0
- Added support for cross-axis alignment.
## 1.0.0
- Complete overhaul of the widget architecture. Now, no custom widget is
accepted as a child, but only text. This is a limitation that was consciously
made to allow several other features to be implemented, including:
- More efficient scrolling that resets after every round.
- Backwards scrolling.
- Pauses after every round.
- Custom durations and curves for accelerating and decelerating.
- Switched to async handling of scrolling instead of relying on Timer,
resulting in a more consistent experience when resuming from a paused app
state.
- Start padding added.
- API documentation greatly improved. Added many examples.
- README is more concise.
## 0.1.0
- Added example.
## 0.0.2
- Improved readme.
- Thanks to @MohiuddinM
## 0.0.1
- Initial release featuring custom `scrollAxis`, `blankSpace`, and `velocity`.
================================================
FILE: LICENSE
================================================
Copyright (c) 2018 Marcel Garus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
⏩ A Flutter widget that scrolls text infinitely. Provides many customizations
including custom scroll directions, durations, curves as well as pauses after
every round.
*Appreciate the widget? Show some ❤️ and star the repo to support the project.*
## Usage
This is a minimalistic example:
```dart
Marquee(
text: 'There once was a boy who told this story about a boy: "',
)
```
And here's a piece of code that makes full use of the marquee's
customizability:
```dart
Marquee(
text: 'Some sample text that takes some space.',
style: TextStyle(fontWeight: FontWeight.bold),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 20.0,
velocity: 100.0,
pauseAfterRound: Duration(seconds: 1),
startPadding: 10.0,
accelerationDuration: Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
)
```
For more information about the properties, have a look at the
[API reference](https://pub.dartlang.org/documentation/marquee/).
================================================
FILE: example/.gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
================================================
FILE: example/.metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 5f21edf8b66e31a39133177319414395cc5b5f48
channel: stable
project_type: app
================================================
FILE: example/README.md
================================================
# marquee example
A Flutter project that highlights how to use the `Marquee` widget.
================================================
FILE: example/lib/main.dart
================================================
import 'package:flutter/material.dart';
import 'package:marquee/marquee.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _useRtlText = false;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Marquee',
home: Scaffold(
backgroundColor: Colors.deepOrange,
body: ListView(
padding: EdgeInsets.only(top: 50),
children: [
_buildMarquee(),
_buildComplexMarquee(),
].map(_wrapWithStuff).toList(),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => setState(() => _useRtlText = !_useRtlText),
label: !_useRtlText
? const Text('Switch to Hebrew')
: const Text('החלף לאנגלית'),
backgroundColor: Colors.pink,
),
),
);
}
Widget _buildMarquee() {
return Marquee(
key: Key("$_useRtlText"),
text: !_useRtlText
? 'There once was a boy who told this story about a boy: "'
: 'פעם היה ילד אשר סיפר סיפור על ילד:"',
velocity: 50.0,
);
}
Widget _buildComplexMarquee() {
return Marquee(
key: Key("$_useRtlText"),
text: !_useRtlText
? 'Some sample text that takes some space.'
: 'זהו משפט ראשון של הטקסט הארוך. זהו המשפט השני של הטקסט הארוך',
style: TextStyle(fontWeight: FontWeight.bold),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 20,
velocity: 100,
pauseAfterRound: Duration(seconds: 1),
showFadingOnlyWhenScrolling: true,
fadingEdgeStartFraction: 0.1,
fadingEdgeEndFraction: 0.1,
numberOfRounds: 3,
startPadding: 10,
accelerationDuration: Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
textDirection: _useRtlText ? TextDirection.rtl : TextDirection.ltr,
);
}
Widget _wrapWithStuff(Widget child) {
return Padding(
padding: EdgeInsets.all(16),
child: Container(height: 50, color: Colors.white, child: child),
);
}
}
================================================
FILE: example/pubspec.yaml
================================================
name: example
description: 'An example of how to use the Marquee widget.'
version: 1.0.0
publish_to: none
repository: https://github.com/marcelgarus/marquee
environment:
sdk: '>=2.17.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
marquee:
path: ..
dev_dependencies:
flutter_test:
sdk: flutter
================================================
FILE: lib/marquee.dart
================================================
library marquee;
import 'dart:async';
import 'package:fading_edge_scrollview/fading_edge_scrollview.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
/// A curve that represents the integral of another curve.
///
/// The constructor takes an other curve and calculates the integral. The
/// values of this curve are then being normalized onto the interval from 0 to
/// 1, but the integration value can always be obtained using the [interval]
/// property.
class _IntegralCurve extends Curve {
/// Delta for integrating.
static double delta = 0.01;
_IntegralCurve._(this.original, this.integral, this._values);
/// The original curve that was integrated.
final Curve original;
/// The integral value.
final double integral;
/// Cached cumulative values of the integral.
final Map<double, double> _values;
/// The constructor that takes the [original] curve.
factory _IntegralCurve(Curve original) {
double integral = 0.0;
final values = Map<double, double>();
for (double t = 0.0; t <= 1.0; t += delta) {
integral += original.transform(t) * delta;
values[t] = integral;
}
values[1.0] = integral;
// Normalize.
for (final double t in values.keys) values[t] = values[t]! / integral;
return _IntegralCurve._(original, integral, values);
}
/// Transforms a value to the normalized integrated value of the [original]
/// curve.
double transform(double t) {
if (t < 0) return 0.0;
for (final key in _values.keys) if (key > t) return _values[key]!;
return 1.0;
}
}
/// A widget that repeats text and automatically scrolls it infinitely.
///
/// ## Sample code
///
/// This is a minimalistic example:
///
/// ```dart
/// Marquee(
/// text: 'There once was a boy who told this story about a boy: "',
/// )
/// ```
///
/// And here's a piece of code that makes full use of the marquee's
/// customizability:
///
/// ```dart
/// Marquee(
/// text: 'Some sample text that takes some space.',
/// style: TextStyle(fontWeight: FontWeight.bold),
/// scrollAxis: Axis.horizontal,
/// blankSpace: 20.0,
/// velocity: 100.0,
/// pauseAfterRound: Duration(seconds: 1),
/// startPadding: 10.0,
/// accelerationDuration: Duration(seconds: 1),
/// accelerationCurve: Curves.linear,
/// decelerationDuration: Duration(milliseconds: 500),
/// decelerationCurve: Curves.easeOut,
/// )
/// ```
///
/// See also:
///
/// * [ListView.builder], where by returning the same widget to the builder
/// every time, a similar result can be achieved, just without the automatic
/// scrolling and manual scrolling enabled.
class Marquee extends StatefulWidget {
Marquee({
super.key,
required this.text,
this.style,
this.textScaleFactor,
this.textDirection = TextDirection.ltr,
this.scrollAxis = Axis.horizontal,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.blankSpace = 0.0,
this.velocity = 50.0,
this.startAfter = Duration.zero,
this.pauseAfterRound = Duration.zero,
this.showFadingOnlyWhenScrolling = true,
this.fadingEdgeStartFraction = 0.0,
this.fadingEdgeEndFraction = 0.0,
this.numberOfRounds,
this.startPadding = 0.0,
this.accelerationDuration = Duration.zero,
Curve accelerationCurve = Curves.decelerate,
this.decelerationDuration = Duration.zero,
Curve decelerationCurve = Curves.decelerate,
this.onDone,
}) : assert(!blankSpace.isNaN),
assert(blankSpace >= 0, "The blankSpace needs to be positive or zero."),
assert(blankSpace.isFinite),
assert(!velocity.isNaN),
assert(velocity != 0.0, "The velocity cannot be zero."),
assert(velocity.isFinite),
assert(
pauseAfterRound >= Duration.zero,
"The pauseAfterRound cannot be negative as time travel isn't "
"invented yet.",
),
assert(
fadingEdgeStartFraction >= 0 && fadingEdgeStartFraction <= 1,
"The fadingEdgeGradientFractionOnStart value should be between 0 and "
"1, inclusive",
),
assert(
fadingEdgeEndFraction >= 0 && fadingEdgeEndFraction <= 1,
"The fadingEdgeGradientFractionOnEnd value should be between 0 and "
"1, inclusive",
),
assert(numberOfRounds == null || numberOfRounds > 0),
assert(
accelerationDuration >= Duration.zero,
"The accelerationDuration cannot be negative as time travel isn't "
"invented yet.",
),
assert(
decelerationDuration >= Duration.zero,
"The decelerationDuration must be positive or zero as time travel "
"isn't invented yet.",
),
this.accelerationCurve = _IntegralCurve(accelerationCurve),
this.decelerationCurve = _IntegralCurve(decelerationCurve);
/// The text to be displayed.
///
/// See also:
///
/// * [style] to style the text.
final String text;
/// The style of the text to be displayed.
///
/// ## Sample code
///
/// This marquee has a bold text:
///
/// ```dart
/// Marquee(
/// text: 'This is some bold text.',
/// style: TextStyle(weight: FontWeight.bold)
/// )
/// ```
///
/// See also:
///
/// * [text] to provide the text itself.
final TextStyle? style;
/// The font scale of the text to be displayed.
///
/// ## Sample code
///
/// This marquee has a fixed text scale factor, indipendent to the user selected resolution:
///
/// ```dart
/// Marquee(
/// text: 'This is some bold text.',
/// textScaleFactor: 1
/// )
/// ```
///
/// See also:
///
/// * [text] to provide the text itself.
final double? textScaleFactor;
/// The text direction of the text to be displayed.
///
/// ## Sample code
///
/// This marquee has a RTL (Right-to-Left) text:
///
/// ```dart
/// Marquee(
/// text: 'טקסט בעברית',
/// textDirection: TextDirection.rtl
/// )
/// ```
///
/// See also:
///
/// * [text] to provide the text itself.
final TextDirection textDirection;
/// The scroll axis.
///
/// If set to [Axis.horizontal], the default scrolling direction is to the
/// right.
/// If set to [Axis.vertical], the default scrolling direction is to the
/// bottom.
///
/// ## Sample code
///
/// This marquee scrolls vertically:
///
/// ```dart
/// Marquee(
/// scrollAxis: Axis.vertical,
/// text: "Look what's below this:",
/// )
/// ```
///
/// See also:
///
/// * The sign of [velocity] to define the concrete scroll direction on this
/// axis.
final Axis scrollAxis;
/// The alignment along the cross axis.
///
/// # Sample code
///
/// ```-dart
/// Marquee(
/// crossAxisAlignment: CrossAxisAlignment.start,
/// )
/// ```
///
/// See also:
///
/// * [scrollAxis] for setting the primary axis.
final CrossAxisAlignment crossAxisAlignment;
/// The extend of blank space to display between instances of the text.
///
/// ## Sample code
///
/// In this example, there's 300 density pixels between the text instances:
///
/// ```dart
/// Marquee(
/// blankSpace: 300.0,
/// child: 'Wait for it...',
/// )
/// ```
final double blankSpace;
/// The scrolling velocity in pixels per second.
///
/// If a negative velocity is provided, the marquee scrolls in the reverse
/// direction (to the right for horizontal marquees and to the top for
/// vertical ones).
///
/// ## Sample code
///
/// This marquee scrolls backwards with 1000 pixels per second:
///
/// ```dart
/// Marquee(
/// velocity: -1000.0,
/// text: 'Gotta go fast in the reverse direction',
/// )
/// ```
///
/// See also:
///
/// * [scrollAxis] to change the axis in which the scrolling takes place.
final double velocity;
/// Start scrolling after this duration after the widget is first displayed.
///
/// ## Sample code
///
/// This [Marquee] starts scrolling one second after being displayed.
///
/// ```dart
/// Marquee(
/// startAfter: const Duration(seconds: 1),
/// text: 'Starts one second after being displayed.',
/// )
/// ```
final Duration startAfter;
/// After each round, a pause of this duration occurs.
///
/// ## Sample code
///
/// After every round, this marquee pauses for one second.
///
/// ```dart
/// Marquee(
/// pauseAfterRound: const Duration(seconds: 1),
/// text: 'Pausing for some time after every round.',
/// )
/// ```
///
/// See also:
///
/// * [accelerationDuration] and [decelerationDuration] to make the
/// transitions between moving and paused state smooth.
/// * [accelerationCurve] and [decelerationCurve] to have more control about
/// how the transition between moving and pausing state occur.
final Duration pauseAfterRound;
/// When the text scrolled around [numberOfRounds] times, it will stop scrolling
/// `null` indicates there is no limit
///
/// ## Sample code
///
/// This marquee stops after 3 rounds
///
/// ```dart
/// Marquee(
/// numberOfRounds:3,
/// text: 'Stopping after three rounds.'
/// )
/// ```
final int? numberOfRounds;
/// Whether the fading edge should only appear while the text is
/// scrolling.
///
/// ## Sample code
///
/// This marquee will only show the fade while scrolling.
///
/// ```dart
/// Marquee(
/// showFadingOnlyWhenScrolling: true,
/// fadingEdgeStartFraction: 0.1,
/// fadingEdgeEndFraction: 0.1,
/// text: 'Example text.',
/// )
/// ```
final bool showFadingOnlyWhenScrolling;
/// The fraction of the [Marquee] that will be faded on the left or top.
/// By default, there won't be any fading.
///
/// ## Sample code
///
/// This marquee fades the edges while scrolling
///
/// ```dart
/// Marquee(
/// showFadingOnlyWhenScrolling: true,
/// fadingEdgeStartFraction: 0.1,
/// fadingEdgeEndFraction: 0.1,
/// text: 'Example text.',
/// )
/// ```
final double fadingEdgeStartFraction;
/// The fraction of the [Marquee] that will be faded on the right or down.
/// By default, there won't be any fading.
///
/// ## Sample code
///
/// This marquee fades the edges while scrolling
///
/// ```dart
/// Marquee(
/// showFadingOnlyWhenScrolling: true,
/// fadingEdgeStartFraction: 0.1,
/// fadingEdgeEndFraction: 0.1,
/// text: 'Example text.',
/// )
/// ```
final double fadingEdgeEndFraction;
/// A padding for the resting position.
///
/// In between rounds, the marquee stops at this position. This parameter is
/// especially useful if the marquee pauses after rounds and some other
/// widgets are stacked on top of the marquee and block the sides, like
/// fading gradients.
///
/// ## Sample code
///
/// ```dart
/// Marquee(
/// startPadding: 20.0,
/// pauseAfterRound: Duration(seconds: 1),
/// text: "During pausing, this text is shifted 20 pixel to the right."
/// )
/// ```
final double startPadding;
/// How long the acceleration takes.
///
/// At the start of each round, the scrolling speed gains momentum for this
/// duration. This parameter is only useful if you embrace a pause after
/// every round.
///
/// ## Sample code
///
/// A marquee that slowly accelerates in two seconds.
///
/// ```dart
/// Marquee(
/// accelerationDuration: Duration(seconds: 2),
/// text: 'Gaining momentum in two seconds.'
/// )
/// ```
///
/// See also:
///
/// * [accelerationCurve] to define a custom curve for accelerating.
/// * [decelerationDuration], the equivalent for decelerating.
final Duration accelerationDuration;
/// The acceleration at the start of each round.
///
/// At the start of each round, the acceleration is defined by this curve
/// where 0.0 stands for not moving and 1.0 for the target [velocity].
/// Notice that it's useless to set the curve if you leave the
/// [accelerationDuration] at the default of [Duration.zero].
/// Also notice that you don't provide the scroll positions, but the actual
/// velocity, so this curve gets integrated.
///
/// ## Sample code
///
/// A marquee that accelerates with a custom curve.
///
/// ```dart
/// Marquee(
/// accelerationDuration: Duration(seconds: 1),
/// accelerationCurve: Curves.easeInOut,
/// text: 'Accelerating with a custom curve.'
/// )
/// ```
///
/// See also:
///
/// * [accelerationDuration] to change the duration of the acceleration.
/// * [decelerationCurve], the equivalent for decelerating.
final _IntegralCurve accelerationCurve;
/// How long the deceleration takes.
///
/// At the end of each round, the scrolling speed gradually comes to a
/// halt in this duration. This parameter is only useful if you embrace a
/// pause after every round.
///
/// ## Sample code
///
/// A marquee that gradually comes to a halt in two seconds.
///
/// ```dart
/// Marquee(
/// decelerationDuration: Duration(seconds: 2),
/// text: 'Gradually coming to a halt in two seconds.'
/// )
/// ```
///
/// See also:
///
/// * [decelerationCurve] to define a custom curve for accelerating.
/// * [accelerationDuration], the equivalent for decelerating.
final Duration decelerationDuration;
/// The deceleration at the end of each round.
///
/// At the end of each round, the deceleration is defined by this curve where
/// 0.0 stands for not moving and 1.0 for the target [velocity].
/// Notice that it's useless to set this curve if you leave the
/// [accelerationDuration] at the default of [Duration.zero].
/// Also notice that you don't provide the scroll position, but the actual
/// velocity, so this curve gets integrated.
///
/// ## Sample code
///
/// A marquee that decelerates with a custom curve.
///
/// ```dart
/// Marquee(
/// decelerationDuration: Duration(seconds: 1),
/// decelerationCurve: Curves.easeInOut,
/// text: 'Decelerating with a custom curve.'
/// )
/// ```
///
/// See also:
///
/// * [decelerationDuration] to change the duration of the acceleration.
/// * [accelerationCurve], the equivalent for decelerating.
final _IntegralCurve decelerationCurve;
/// This function will be called if [numberOfRounds] is set and the [Marquee]
/// finished scrolled the specified number of rounds.
final VoidCallback? onDone;
@override
State<StatefulWidget> createState() => _MarqueeState();
}
class _MarqueeState extends State<Marquee> with SingleTickerProviderStateMixin {
/// The controller for the scrolling behavior.
final ScrollController _controller = ScrollController();
// The scroll positions at various scrolling phases.
late double _startPosition; // At the start, before accelerating.
late double
_accelerationTarget; // After accelerating, before moving linearly.
late double _linearTarget; // After moving linearly, before decelerating.
late double _decelerationTarget; // After decelerating.
// The durations of various scrolling phases.
late Duration _totalDuration;
Duration get _accelerationDuration => widget.accelerationDuration;
Duration? _linearDuration; // The duration of linearly scrolling.
Duration get _decelerationDuration => widget.decelerationDuration;
/// A timer that is fired at the start of each round.
bool _running = false;
bool _isOnPause = false;
int _roundCounter = 0;
bool get isDone => widget.numberOfRounds == null
? false
: widget.numberOfRounds == _roundCounter;
bool get showFading =>
!widget.showFadingOnlyWhenScrolling ? true : !_isOnPause;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!_running) {
_running = true;
if (_controller.hasClients) {
_controller.jumpTo(_startPosition);
await Future<void>.delayed(widget.startAfter);
Future.doWhile(_scroll);
}
}
});
}
Future<bool> _scroll() async {
await _makeRoundTrip();
if (isDone && widget.onDone != null) {
widget.onDone!();
}
return _running && !isDone && _controller.hasClients;
}
@override
void didUpdateWidget(Widget oldWidget) {
super.didUpdateWidget(oldWidget as Marquee);
}
@override
void dispose() {
_running = false;
_controller.dispose();
super.dispose();
}
/// Calculates all necessary values for animating, then starts the animation.
void _initialize(BuildContext context) {
// Calculate lengths (amount of pixels that each phase needs).
final totalLength = _getTextWidth(context) + widget.blankSpace;
final accelerationLength = widget.accelerationCurve.integral *
widget.velocity *
_accelerationDuration.inMilliseconds /
1000.0;
final decelerationLength = widget.decelerationCurve.integral *
widget.velocity *
_decelerationDuration.inMilliseconds /
1000.0;
final linearLength =
(totalLength - accelerationLength.abs() - decelerationLength.abs()) *
(widget.velocity > 0 ? 1 : -1);
// Calculate scroll positions at various scrolling phases.
_startPosition = 2 * totalLength - widget.startPadding;
_accelerationTarget = _startPosition + accelerationLength;
_linearTarget = _accelerationTarget + linearLength;
_decelerationTarget = _linearTarget + decelerationLength;
// Calculate durations for the phases.
_totalDuration = _accelerationDuration +
_decelerationDuration +
Duration(milliseconds: (linearLength / widget.velocity * 1000).toInt());
_linearDuration =
_totalDuration - _accelerationDuration - _decelerationDuration;
assert(
_totalDuration > Duration.zero,
"With the given values, the total duration for one round would be "
"negative. As time travel isn't invented yet, this shouldn't happen.",
);
assert(
_linearDuration! >= Duration.zero,
"Acceleration and deceleration phase overlap. To fix this, try a "
"combination of these approaches:\n"
"* Make the text longer, so there's more room to animate within.\n"
"* Shorten the accelerationDuration or decelerationDuration.\n"
"* Decrease the velocity, so the duration to animate within is longer.\n",
);
}
/// Causes the controller to scroll one round.
Future<void> _makeRoundTrip() async {
// Reset the controller, then accelerate, move linearly and decelerate.
if (!_controller.hasClients) return;
_controller.jumpTo(_startPosition);
if (!_running) return;
await _accelerate();
if (!_running) return;
await _moveLinearly();
if (!_running) return;
await _decelerate();
_roundCounter++;
if (!_running || !mounted) return;
if (widget.pauseAfterRound > Duration.zero) {
setState(() => _isOnPause = true);
await Future.delayed(widget.pauseAfterRound);
if (!mounted || isDone) return;
setState(() => _isOnPause = false);
}
}
// Methods that animate the controller.
Future<void> _accelerate() async {
await _animateTo(
_accelerationTarget,
_accelerationDuration,
widget.accelerationCurve,
);
}
Future<void> _moveLinearly() async {
await _animateTo(_linearTarget, _linearDuration, Curves.linear);
}
Future<void> _decelerate() async {
await _animateTo(
_decelerationTarget,
_decelerationDuration,
widget.decelerationCurve.flipped,
);
}
/// Helping method that either animates to the given target position or jumps
/// right to it if the duration is Duration.zero.
Future<void> _animateTo(
double? target,
Duration? duration,
Curve curve,
) async {
if (!_controller.hasClients) return;
if (duration! > Duration.zero) {
await _controller.animateTo(target!, duration: duration, curve: curve);
} else {
_controller.jumpTo(target!);
}
}
/// Returns the width of the text.
double _getTextWidth(BuildContext context) {
final span = TextSpan(text: widget.text, style: widget.style);
final constraints = BoxConstraints(maxWidth: double.infinity);
final richTextWidget = Text.rich(span).build(context) as RichText;
final renderObject = richTextWidget.createRenderObject(context);
renderObject.layout(constraints);
final boxes = renderObject.getBoxesForSelection(TextSelection(
baseOffset: 0,
extentOffset: TextSpan(text: widget.text).toPlainText().length,
));
return boxes.last.right;
}
@override
Widget build(BuildContext context) {
_initialize(context);
bool isHorizontal = widget.scrollAxis == Axis.horizontal;
Alignment? alignment;
switch (widget.crossAxisAlignment) {
case CrossAxisAlignment.start:
alignment = isHorizontal ? Alignment.topCenter : Alignment.centerLeft;
break;
case CrossAxisAlignment.end:
alignment =
isHorizontal ? Alignment.bottomCenter : Alignment.centerRight;
break;
case CrossAxisAlignment.center:
alignment = Alignment.center;
break;
case CrossAxisAlignment.stretch:
case CrossAxisAlignment.baseline:
alignment = null;
break;
}
Widget marquee = ListView.builder(
controller: _controller,
scrollDirection: widget.scrollAxis,
reverse: widget.textDirection == TextDirection.rtl,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (_, i) {
final text = i.isEven
? Text(
widget.text,
style: widget.style,
textScaler: widget.textScaleFactor != null
? TextScaler.linear(widget.textScaleFactor!)
: null,
)
: _buildBlankSpace();
return alignment == null
? text
: Align(alignment: alignment, child: text);
},
);
return kIsWeb ? marquee : _wrapWithFadingEdgeScrollView(marquee);
}
/// Builds the blank space between children.
Widget _buildBlankSpace() {
return SizedBox(
width: widget.scrollAxis == Axis.horizontal ? widget.blankSpace : null,
height: widget.scrollAxis == Axis.vertical ? widget.blankSpace : null,
);
}
Widget _wrapWithFadingEdgeScrollView(Widget child) {
return FadingEdgeScrollView.fromScrollView(
gradientFractionOnStart:
!showFading ? 0.0 : widget.fadingEdgeStartFraction,
gradientFractionOnEnd: !showFading ? 0.0 : widget.fadingEdgeEndFraction,
child: child as ScrollView,
);
}
}
================================================
FILE: pubspec.yaml
================================================
name: marquee
description: "A Flutter widget that scrolls text infinitely. Provides many
customizations including custom scroll directions, durations, curves as well
as pauses after every round."
version: 2.3.0
repository: https://github.com/MarcelGarus/marquee
environment:
flutter: ">=3.0.0"
sdk: "^3.5.0"
dependencies:
fading_edge_scrollview: ^4.1.1
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
gitextract_wvwhnoz3/ ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── lib/ │ │ └── main.dart │ └── pubspec.yaml ├── lib/ │ └── marquee.dart └── pubspec.yaml
SYMBOL INDEX (27 symbols across 2 files)
FILE: example/lib/main.dart
function main (line 4) | void main()
class MyApp (line 6) | class MyApp extends StatefulWidget {
method createState (line 8) | _MyAppState createState()
class _MyAppState (line 11) | class _MyAppState extends State<MyApp> {
method build (line 15) | Widget build(BuildContext context)
method _buildMarquee (line 38) | Widget _buildMarquee()
method _buildComplexMarquee (line 48) | Widget _buildComplexMarquee()
method _wrapWithStuff (line 73) | Widget _wrapWithStuff(Widget child)
FILE: lib/marquee.dart
class _IntegralCurve (line 15) | class _IntegralCurve extends Curve {
method transform (line 49) | double transform(double t)
class Marquee (line 92) | class Marquee extends StatefulWidget {
method createState (line 510) | State<StatefulWidget> createState()
class _MarqueeState (line 513) | class _MarqueeState extends State<Marquee> with SingleTickerProviderStat...
method initState (line 542) | void initState()
method _scroll (line 556) | Future<bool> _scroll()
method didUpdateWidget (line 565) | void didUpdateWidget(Widget oldWidget)
method dispose (line 570) | void dispose()
method _initialize (line 577) | void _initialize(BuildContext context)
method _makeRoundTrip (line 621) | Future<void> _makeRoundTrip()
method _accelerate (line 650) | Future<void> _accelerate()
method _moveLinearly (line 658) | Future<void> _moveLinearly()
method _decelerate (line 662) | Future<void> _decelerate()
method _animateTo (line 672) | Future<void> _animateTo(
method _getTextWidth (line 686) | double _getTextWidth(BuildContext context)
method build (line 704) | Widget build(BuildContext context)
method _buildBlankSpace (line 752) | Widget _buildBlankSpace()
method _wrapWithFadingEdgeScrollView (line 759) | Widget _wrapWithFadingEdgeScrollView(Widget child)
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
{
"path": ".gitignore",
"chars": 166,
"preview": ".DS_Store\n.dart_tool/\n\n.packages\n.pub/\n.idea/\n.vscode/\n*.lock\n\nbuild/\nandroid/\nios/.generated/\nios/Flutter/Generated.xcc"
},
{
"path": ".metadata",
"chars": 284,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "CHANGELOG.md",
"chars": 2540,
"preview": "## 2.3.0\n\n- Update `fading_edge_scrollview` dependency to `4.1.1` to support flutter 3.24\n- Addded @dispose call for con"
},
{
"path": "LICENSE",
"chars": 1056,
"preview": "Copyright (c) 2018 Marcel Garus\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this so"
},
{
"path": "README.md",
"chars": 1072,
"preview": "⏩ A Flutter widget that scrolls text infinitely. Provides many customizations\nincluding custom scroll directions, durati"
},
{
"path": "example/.gitignore",
"chars": 692,
"preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.i"
},
{
"path": "example/.metadata",
"chars": 305,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "example/README.md",
"chars": 86,
"preview": "# marquee example\n\nA Flutter project that highlights how to use the `Marquee` widget.\n"
},
{
"path": "example/lib/main.dart",
"chars": 2321,
"preview": "import 'package:flutter/material.dart';\nimport 'package:marquee/marquee.dart';\n\nvoid main() => runApp(MyApp());\n\nclass M"
},
{
"path": "example/pubspec.yaml",
"chars": 315,
"preview": "name: example\ndescription: 'An example of how to use the Marquee widget.'\nversion: 1.0.0\npublish_to: none\nrepository: ht"
},
{
"path": "lib/marquee.dart",
"chars": 22826,
"preview": "library marquee;\n\nimport 'dart:async';\n\nimport 'package:fading_edge_scrollview/fading_edge_scrollview.dart';\nimport 'pac"
},
{
"path": "pubspec.yaml",
"chars": 444,
"preview": "name: marquee\ndescription: \"A Flutter widget that scrolls text infinitely. Provides many\n customizations including cust"
}
]
About this extraction
This page contains the full source code of the MarcelGarus/marquee GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (31.4 KB), approximately 8.2k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.