需要帮助在 Flutter 中复制 Audible 的滚动文本功能 - 我这样做的方式正确吗?

问题描述 投票:0回答:1

我对 flutter 还很陌生,我正在努力复制 Audible 应用程序中长曲目标题的文本滚动功能(参见附件)。这是我想要实现的行为:

• 最初显示长标题 - 其中一部分将不可见,因为它太长。

• 暂停预定的秒数,文本应在此暂停状态下等待几秒钟,然后再开始滚动。

• 开始滚动文本。

• 确保通过滚动显示整个文本。

• 在文本末尾插入特定数量的空格。

• 当空格结束时,我希望文本的开头重新出现并继续滚动。

• 文本应该滚动,直到看起来回到原来的位置。

• 重新开始循环(等待 -> 滚动 -> 循环)。

我已经成功实现了其中的大部分内容。然而,我的重置点并不完美(见附件)。我复制了文本以使其滚动两次,然后当第二组到达第一个文本的开始位置时重置动画。时间不精确,导致重置不完美。

只是觉得可能有比我所做的更好的方法来解决这个问题,但我对 Flutter 很陌生,我不知道那种方法可能是什么。

有没有更有效的方法来实现这一点,或者有人有改进我现有代码的建议吗?

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'dart:async';
import 'package:the_audiobook_app/utils/audio_player_handler.dart';

class ScrollingText extends StatefulWidget {
  final String? text;
  final double speed;
  final double containerWidth;
  final double containerHeight;
  final int spaceCount;
  final Duration delay;

  ScrollingText({
    this.text,
    this.speed = 1.0,
    this.containerWidth = 150.0,
    this.containerHeight = 50.0,
    this.spaceCount = 5,
    this.delay = const Duration(seconds: 2),
  });


  @override
  _ScrollingTextState createState() => _ScrollingTextState();
}

class _ScrollingTextState extends State<ScrollingText> with TickerProviderStateMixin {
  late ScrollController _controller;
  late Ticker _ticker;
  late TextPainter textPainter;
  bool shouldScroll = false;
  late String spacedText;  // Added this to ensure spaceCount is applied consistently
  double singleTextWidth = 0;

  @override
  void initState() {
    super.initState();

    _controller = ScrollController();
    _ticker = Ticker(_onTick);
    //spacedText = widget.text + ' ' * widget.spaceCount + widget.text;
    //spacedText = 'Loading...${' ' * widget.spaceCount}Loading...';  // The fact this is being set here is probably not a good thing
  }

  void initializeScrollingText(String title){
    spacedText = title + ' ' * widget.spaceCount + title;

    textPainter = TextPainter(
      text: TextSpan(text: title, style: TextStyle(fontSize: 20)),
      textDirection: TextDirection.ltr,
    )..layout();

    singleTextWidth = textPainter.width;

    if (textPainter.width >= widget.containerWidth) {
      textPainter.text = TextSpan(text: spacedText, style: TextStyle(fontSize: 20));
      textPainter.layout();
    }

    shouldScroll = textPainter.width > widget.containerWidth;

    if (shouldScroll && !_ticker.isActive) {
      _startScrollingWithDelay();
    }
  }

  void _startScrollingWithDelay() {
    Future.delayed(widget.delay, () {
      if (mounted) {
        _ticker.start();
      }
    });
  }

  void _onTick(Duration elapsed) {
    double current = _controller.offset + widget.speed;

    if (current >= singleTextWidth) { // When the second instance is at the beginning.
      _ticker.stop();
      _controller.jumpTo(0);

      Future.delayed(widget.delay, () {
        if (mounted) {
          _ticker.start();
        }
      });
    } else {
      _controller.jumpTo(current);
    }
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int?>(
      stream: AudioPlayerHandler.instance.player.currentIndexStream,
      builder: (context, snapshot) {
        String title = 'Loading...';  // default text
        if (snapshot.connectionState == ConnectionState.active && snapshot.data != null) {
          title = AudioPlayerHandler.instance.getCurrentTrackTitle() ?? 'NULL VALUE';
        }
        initializeScrollingText(title);  // Initialize scrolling for the new title

        return Container(
          width: widget.containerWidth,
          height: widget.containerHeight,
          child: shouldScroll
              ? ListView.builder(
            itemCount: 1,
            controller: _controller,
            scrollDirection: Axis.horizontal,
            itemBuilder: (BuildContext context, int index) {
              return Text(spacedText,
                  style: TextStyle(fontSize: 20),
                  softWrap: false,
                  overflow: TextOverflow.visible);
            },
          )
              : Center(
            child: Text(spacedText.split(' ')[0], // Since spacedText has repetitions, we only take the first occurrence
                style: TextStyle(fontSize: 20),
                softWrap: true,
                overflow: TextOverflow.ellipsis),
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    _ticker.dispose();
    _controller.dispose();
    super.dispose();
  }
}
flutter scroll flutter-animation ui-design flutter-text
1个回答
0
投票

这种滚动动画称为“跑马灯”。您正是需要这个扩展:https://pub.dev/packages/text_scroll

我为你的案例写了一个例子;

 Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Row(
        children: [
          const Expanded(flex: 2, child: Text('Static text')),
          Flexible(
            flex: 1,
            child: Container(
              color: Colors.grey,
              padding: const EdgeInsets.symmetric(vertical: 5),
              child: const TextScroll(
                'This is the sample text for Flutter TextScroll widget. ',
                velocity: Velocity(pixelsPerSecond: Offset(50, 0)),
                pauseBetween: Duration(milliseconds: 2000), // This will pause animation after it ends. You need this for your case
                mode: TextScrollMode.endless, 
              ),
            ),
          ),
        ],
      ),
    ],
  )

祝你好运👋

© www.soinside.com 2019 - 2024. All rights reserved.