scroll_texts_widget 0.1.2
scroll_texts_widget: ^0.1.2 copied to clipboard
Smooth horizontal marquee scrolling text ticker for Flutter. Supports streaming for long texts, cached rendering, and pause/progress controls.
scroll_texts_widget #
A highly efficient and modular Flutter widget for creating scrolling marquee text banners.
Unlike standard marquee widgets that struggle with long strings, force full widget-tree rebuilds, or warp speeds due to device animation scales, scroll_texts_widget uses direct Canvas rendering via CustomPainter and a standalone Ticker to guarantee smooth, 60/120fps scrolling.
✨ Key Features #
- 🪟 Dual Render Modes:
- Cached Mode: Measures text once and clips on the GPU. Delivers 100% typographic fidelity (BiDi, complex scripts, kerning) with zero per-frame layout overhead.
- Streaming Mode: Employs a sliding window algorithm that measures and renders only the words/chunks currently visible inside the viewport box. Retains strictly $O(\text{viewport})$ memory and eliminates UI freezes even with 50,000-word strings.
- Auto Mode (Default): Automatically chooses the best mode based on string length.
- 🎮 Full Controller Support: Use
ScrollTextsControllerto programmatically play, pause, toggle pause, read the exact pixel offset, inspect the active text index, or jump directly to any position or announcement in the list. - ⏱️ Consistent Velocity (Pixels/Sec): Configured via
scrollSpeed(px/s), guaranteeing uniform visual velocity regardless of text length. - ⚙️ Platform-Independent Frame Timing: Uses a direct
Tickerwith microsecond delta-time tracking ($\Delta x = \text{speed} \times \Delta t$), making the scroll rate immune to OS animation scaling and ProMotion 120Hz refresh rates. - 🌍 First-Class RTL Support: Full support for
TextDirection.rtl(Arabic, Hebrew) adjusting layout and scrolling direction. - 📏 Intrinsic Auto-Sizing: Measures font metrics upfront to match the exact text height, preventing layout overflow errors.
💻 Usage #
Basic Example #
import 'package:flutter/material.dart';
import 'package:scroll_texts_widget/scroll_texts_widget.dart';
class MyMarqueeApp extends StatelessWidget {
const MyMarqueeApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Marquee Demo')),
body: Center(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8.0),
color: Colors.blueGrey.shade100,
child: ScrollTextsWidget(
texts: const [
'Welcome to the scroll_texts_widget package.',
'This text will scroll at a consistent 75 pixels per second!',
],
textStyle: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
scrollSpeed: 75.0,
pauseDuration: const Duration(seconds: 1),
),
),
),
);
}
}
Advanced Example with Controller & Callbacks #
final controller = ScrollTextsController(
initialScrollOffset: 0.0,
initialTextIndex: 0,
);
// Programmatic controls
controller.pause();
controller.resume();
controller.togglePause();
controller.jumpTo(150.0); // Jump to 150px in the current text
controller.jumpToText(2); // Skip to 3rd announcement
ScrollTextsWidget(
texts: myAnnouncements,
controller: controller,
renderMode: ScrollTextRenderMode.auto,
scrollSpeed: 90.0,
pauseDuration: const Duration(seconds: 2),
textDirection: TextDirection.ltr,
onScrollChanged: (offset, textIndex) {
print('Scrolled to offset: $offset on text #$textIndex');
},
onTextCompleted: (completedIndex) {
print('Completed text #$completedIndex');
},
)
🍳 Cookbook & Common Recipes #
scroll_texts_widget adheres strictly to Flutter's composition philosophy: rather than bloating the core canvas engine with mouse trackers, gesture disambiguators, or decorative wrappers that penalize mobile and embedded performance, the widget exposes a reactive ScrollTextsController so you can compose exactly what you need with zero overhead.
1. Pause on Hover (Desktop & Web) #
Pause the ticker when the cursor hovers over it, and resume when it leaves:
final controller = ScrollTextsController();
MouseRegion(
onEnter: (_) => controller.pause(),
onExit: (_) => controller.resume(),
child: ScrollTextsWidget(
texts: myAnnouncements,
controller: controller,
),
);
2. Interactive Tappable Headlines #
Detect taps on the active headline to navigate or open news details:
final controller = ScrollTextsController();
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
final activeIndex = controller.currentTextIndex;
final activeHeadline = myAnnouncements[activeIndex];
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => ArticlePage(headline: activeHeadline)),
);
},
child: ScrollTextsWidget(
texts: myAnnouncements,
controller: controller,
),
);
3. Soft Edge Fading (Gradient Fade) #
Fade the text seamlessly as it enters and leaves the viewport edges using ShaderMask:
ShaderMask(
shaderCallback: (rect) {
return const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Colors.transparent,
Colors.black,
Colors.black,
Colors.transparent,
],
stops: [0.0, 0.08, 0.92, 1.0],
).createShader(rect);
},
blendMode: BlendMode.dstIn,
child: ScrollTextsWidget(
texts: myAnnouncements,
),
);
4. Platform-Adaptive Composition #
Only register MouseRegion on desktop and web, keeping iOS, Android, and embedded displays 100% free from mouse-tracker hit testing:
import 'package:flutter/foundation.dart';
Widget ticker = ScrollTextsWidget(
texts: myAnnouncements,
controller: controller,
);
if (kIsWeb || defaultTargetPlatform == TargetPlatform.macOS || defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.linux) {
ticker = MouseRegion(
onEnter: (_) => controller.pause(),
onExit: (_) => controller.resume(),
child: ticker,
);
}
⚡ Architecture & Performance Edge #
| Feature | Standard Marquee Packages | scroll_texts_widget |
|---|---|---|
| Engine Architecture | SingleChildScrollView + animateTo() loop |
Direct CustomPainter (Canvas) + standalone Ticker |
| 120Hz ProMotion Stability | Stutters or resets on rebuilds and physics recalculations | Microsecond delta-time ($\Delta x = \text{speed} \times \Delta t$) |
| Data Model | Single String only (requires manual concatenation) |
Native List<String> playlist with pause intervals |
| Memory on Massive Text | Freezes UI laying out 50,000px-wide render trees | Sliding window streaming with $O(\text{viewport})$ memory |
| Lifecycle Overhead | Forces full widget subtree re-layouts per tick | Zero widget tree re-layouts during scrolling |
| Inversion of Control | Hardcoded gestures and hover listeners | Pure canvas core + reactive controller composition |
📖 API Reference #
ScrollTextsWidget Properties #
| Property | Type | Default | Description |
|---|---|---|---|
texts |
List<String> |
Required | The list of strings to cycle and scroll through. |
controller |
ScrollTextsController? |
null |
Optional controller for reading position and programmatic playback/seeking. |
scrollSpeed |
double |
50.0 |
Velocity in pixels per second (px/s). |
pauseDuration |
Duration |
Duration(seconds: 2) |
Duration to pause after a text exits before the next text starts. |
initialDelay |
Duration |
Duration.zero |
Optional delay before the very first text begins scrolling. |
repeat |
bool |
true |
Whether to continuously loop through the text playlist. |
renderMode |
ScrollTextRenderMode |
.auto |
Rendering strategy: .auto, .cached, or .streaming. |
textStyle |
TextStyle |
18px black |
Font style applied to the scrolling text. |
textDirection |
TextDirection |
TextDirection.ltr |
Layout and scroll direction (.ltr scrolls left, .rtl scrolls right). |
initialScrollOffset |
double |
0.0 |
Initial starting pixel offset for the active text. |
initialTextIndex |
int |
0 |
Starting index in texts. |
onScrollChanged |
Function(double, int)? |
null |
Callback invoked as the scroll offset changes. |
onTextCompleted |
Function(int)? |
null |
Callback invoked when an active text finishes scrolling. |
ScrollTextsController API #
| Member | Type | Description |
|---|---|---|
offset |
double (getter) |
The current scroll offset in pixels. |
maxScrollExtent |
double (getter) |
The total scroll distance for the active text. |
progress |
double (getter) |
Normalized scroll progress from 0.0 to 1.0. |
effectiveRenderMode |
ScrollTextRenderMode (getter) |
The active render mode engaged (cached or streaming), useful when auto is selected. |
currentTextIndex |
int (getter) |
The index of the active text in the playlist. |
isPaused |
bool (getter) |
Whether scrolling is currently paused. |
pause() |
void |
Pauses the scrolling animation. |
resume() |
void |
Resumes scrolling. |
togglePause() |
void |
Toggles between paused and scrolling states. |
jumpTo(double offset) |
void |
Instantly jumps the active text to the given pixel offset. |
jumpToText(int textIndex, {double offset = 0.0}) |
void |
Jumps to a specific text in texts at an optional starting offset. |
🛠️ Installation #
Add the package via the Flutter CLI:
flutter pub add scroll_texts_widget
Or add it manually to your pubspec.yaml:
dependencies:
scroll_texts_widget: ^<latest-version>