Flutter MarqueeWidget遇ScrollController未绑定错误,求动态文本长度判断方案
Alright, let's break down why this error pops up when your text is too short, and fix it properly.
Why the Error Happens
Your current code tries to kick off the scroll animation right in initState, but at that point the ScrollController hasn't been attached to the SingleChildScrollView yet. Even worse, when the text fits entirely within its parent container, the scroll view doesn't create a scrollable area at all—so the controller's position property doesn't exist, leading to that crash when you call animateTo.
Step-by-Step Fix
We need two critical changes:
- Only animate when necessary: Run the marquee loop only if the text exceeds the available viewport space.
- Wait for the controller to attach: Make sure we don't interact with the controller until it's properly linked to the scroll view.
Here's the revised, robust version of your MarqueeWidget:
import 'package:flutter/material.dart'; class MarqueeWidget extends StatefulWidget { final Widget child; final Axis direction; final Duration animationDuration, backDuration, pauseDuration; MarqueeWidget({ @required this.child, this.direction: Axis.horizontal, this.animationDuration: const Duration(milliseconds: 7000), this.backDuration: const Duration(milliseconds: 2000), this.pauseDuration: const Duration(milliseconds: 2000), }); @override _MarqueeWidgetState createState() => _MarqueeWidgetState(); } class _MarqueeWidgetState extends State<MarqueeWidget> { ScrollController scrollController = ScrollController(); bool _isScrollNeeded = false; bool _isAnimating = false; @override void initState() { super.initState(); // Wait until the widget is fully built before checking scroll state WidgetsBinding.instance.addPostFrameCallback((_) { _checkScrollRequirement(); if (_isScrollNeeded) _startMarquee(); }); } @override void didUpdateWidget(covariant MarqueeWidget oldWidget) { super.didUpdateWidget(oldWidget); // Re-check if scroll is needed when content changes WidgetsBinding.instance.addPostFrameCallback((_) { _checkScrollRequirement(); if (_isScrollNeeded && !_isAnimating) _startMarquee(); }); } void _checkScrollRequirement() { if (scrollController.hasClients) { setState(() { // Check if content overflows the viewport _isScrollNeeded = scrollController.position.maxScrollExtent > 0; }); } } @override Widget build(BuildContext context) { return SingleChildScrollView( child: widget.child, scrollDirection: widget.direction, controller: scrollController, physics: const NeverScrollableScrollPhysics(), // Block user-initiated scroll ); } void _startMarquee() async { if (!scrollController.hasClients || !_isScrollNeeded) return; _isAnimating = true; try { while (true) { await Future.delayed(widget.pauseDuration); if (!_isScrollNeeded) break; await scrollController.animateTo( scrollController.position.maxScrollExtent, duration: widget.animationDuration, curve: Curves.easeIn, ); await Future.delayed(widget.pauseDuration); if (!_isScrollNeeded) break; await scrollController.animateTo( 0.0, duration: widget.backDuration, curve: Curves.easeOut, ); } } catch (e) { debugPrint("Marquee animation error: $e"); } finally { _isAnimating = false; } } @override void dispose() { scrollController.dispose(); super.dispose(); } }
Key Improvements Explained
- PostFrameCallback: We wait until the widget is fully rendered before checking scroll status—this guarantees the controller is attached to the scroll view.
- _isScrollNeeded Flag: The animation only runs if
maxScrollExtentis greater than 0 (meaning content overflows the viewport). - Dynamic Content Handling:
didUpdateWidgetre-checks scroll requirements if the text changes, so the marquee starts/stops automatically. - Safety Checks: We verify
scrollController.hasClientsbefore accessing its position, and wrap the loop in a try/catch to handle unexpected detachments. - Disabled User Scroll:
NeverScrollableScrollPhysicsprevents user swipes from interfering with the marquee animation.
Usage Reminder
Your original implementation code stays exactly the same—this revised widget will automatically skip animation when text fits perfectly:
MarqueeWidget( direction: Axis.horizontal, child: Text( '$parsedString', // Text length no longer causes crashes style: AppTheme.of(context).caption(), ), )
内容的提问来源于stack exchange,提问作者DolDurma

