Commit cf47b7139673f4e8e213a23b7276e3aa55c6d5bb

Authored by 常守达
1 parent 526cc826

fix(app): 标题文字过长滚动

@@ -3,6 +3,7 @@ import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart'; @@ -3,6 +3,7 @@ import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart';
3 import 'package:doublefeel_flutter/app/modules/home/controllers/my_controller.dart'; 3 import 'package:doublefeel_flutter/app/modules/home/controllers/my_controller.dart';
4 import 'package:doublefeel_flutter/app/routes/app_pages.dart'; 4 import 'package:doublefeel_flutter/app/routes/app_pages.dart';
5 import 'package:doublefeel_flutter/app/utils/dialog_utils.dart'; 5 import 'package:doublefeel_flutter/app/utils/dialog_utils.dart';
  6 +import 'package:doublefeel_flutter/app/widget/marquee_text.dart';
6 7
7 import 'package:doublefeel_flutter/core/network/api/user_api.dart'; 8 import 'package:doublefeel_flutter/core/network/api/user_api.dart';
8 import 'package:doublefeel_flutter/core/result/app_result.dart'; 9 import 'package:doublefeel_flutter/core/result/app_result.dart';
@@ -417,13 +418,9 @@ class _DeleteAccountBottomSheet extends StatelessWidget { @@ -417,13 +418,9 @@ class _DeleteAccountBottomSheet extends StatelessWidget {
417 children: [ 418 children: [
418 Center( 419 Center(
419 child: Padding( 420 child: Padding(
420 - padding: EdgeInsets.symmetric(horizontal: 72),  
421 - child: Text(  
422 - AppLocalizations.of(context)!  
423 - .areYouSureYouWantToDeleteYourAccount,  
424 - maxLines: 1,  
425 - overflow: TextOverflow.ellipsis,  
426 - textAlign: TextAlign.center, 421 + padding: const EdgeInsets.symmetric(horizontal: 54),
  422 + child: MarqueeText(
  423 + text: context.l10n.areYouSureYouWantToDeleteYourAccount,
427 style: TextStyle( 424 style: TextStyle(
428 color: context.colors.textPrimary, 425 color: context.colors.textPrimary,
429 fontSize: 16, 426 fontSize: 16,
  1 +import 'dart:async';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +
  5 +/// 无缝首尾相接的跑马灯文字组件:
  6 +/// - 当文字宽度 <= 容器宽度时:正常居中/按 textAlign 静止展示
  7 +/// - 当文字宽度 > 容器宽度时:文字向左平滑连续滚动,尾部自带留白并首尾无缝衔接循环
  8 +class MarqueeText extends StatefulWidget {
  9 + const MarqueeText({
  10 + super.key,
  11 + required this.text,
  12 + this.style,
  13 + this.textAlign = TextAlign.center,
  14 + this.velocity = 30.0,
  15 + this.blankSpace = 48.0,
  16 + this.startDelay = const Duration(seconds: 1),
  17 + });
  18 +
  19 + final String text;
  20 + final TextStyle? style;
  21 + final TextAlign textAlign;
  22 +
  23 + /// 滚动速度(像素/秒),数值越大滚动越快,建议 25~40
  24 + final double velocity;
  25 +
  26 + /// 首尾循环之间的间隔空白宽度(像素)
  27 + final double blankSpace;
  28 +
  29 + /// 页面加载后首次滚动的初始延迟
  30 + final Duration startDelay;
  31 +
  32 + @override
  33 + State<MarqueeText> createState() => _MarqueeTextState();
  34 +}
  35 +
  36 +class _MarqueeTextState extends State<MarqueeText>
  37 + with SingleTickerProviderStateMixin {
  38 + AnimationController? _controller;
  39 + double _lastTextWidth = 0.0;
  40 + Timer? _delayTimer;
  41 +
  42 + @override
  43 + void dispose() {
  44 + _delayTimer?.cancel();
  45 + _controller?.dispose();
  46 + super.dispose();
  47 + }
  48 +
  49 + void _setupAnimation(double textWidth, double containerWidth) {
  50 + if (textWidth <= containerWidth || containerWidth <= 0) {
  51 + _delayTimer?.cancel();
  52 + _controller?.stop();
  53 + _controller?.dispose();
  54 + _controller = null;
  55 + _lastTextWidth = 0;
  56 + return;
  57 + }
  58 +
  59 + final cycleDistance = textWidth + widget.blankSpace;
  60 + if (_controller != null && (_lastTextWidth - textWidth).abs() < 1.0) {
  61 + return;
  62 + }
  63 +
  64 + _lastTextWidth = textWidth;
  65 + _delayTimer?.cancel();
  66 + _controller?.dispose();
  67 +
  68 + final durationSeconds = cycleDistance / widget.velocity;
  69 + final totalDuration = Duration(
  70 + milliseconds: (durationSeconds * 1000).toInt().clamp(1000, 60000),
  71 + );
  72 +
  73 + final controller = AnimationController(
  74 + vsync: this,
  75 + duration: totalDuration,
  76 + );
  77 + _controller = controller;
  78 +
  79 + _delayTimer = Timer(widget.startDelay, () {
  80 + if (mounted && _controller == controller) {
  81 + controller.repeat();
  82 + }
  83 + });
  84 + }
  85 +
  86 + @override
  87 + Widget build(BuildContext context) {
  88 + final effectiveStyle =
  89 + DefaultTextStyle.of(context).style.merge(widget.style);
  90 +
  91 + return LayoutBuilder(
  92 + builder: (context, constraints) {
  93 + final textPainter = TextPainter(
  94 + text: TextSpan(text: widget.text, style: effectiveStyle),
  95 + textDirection: Directionality.of(context),
  96 + maxLines: 1,
  97 + )..layout();
  98 +
  99 + final textWidth = textPainter.width;
  100 + final maxWidth = constraints.maxWidth;
  101 +
  102 + // 文本未超出容器宽度:普通渲染
  103 + if (textWidth <= maxWidth || maxWidth.isInfinite) {
  104 + _setupAnimation(0, 0);
  105 + return Text(
  106 + widget.text,
  107 + style: widget.style,
  108 + textAlign: widget.textAlign,
  109 + maxLines: 1,
  110 + );
  111 + }
  112 +
  113 + // 文本超出容器宽度:开启首尾无缝循环跑马灯
  114 + _setupAnimation(textWidth, maxWidth);
  115 + final cycleDistance = textWidth + widget.blankSpace;
  116 + final repeatCount = (maxWidth / cycleDistance).ceil() + 2;
  117 +
  118 + final controller = _controller;
  119 + if (controller == null) {
  120 + return Text(
  121 + widget.text,
  122 + style: widget.style,
  123 + maxLines: 1,
  124 + );
  125 + }
  126 +
  127 + return ClipRect(
  128 + child: AnimatedBuilder(
  129 + animation: controller,
  130 + builder: (context, child) {
  131 + final offset = controller.value * cycleDistance;
  132 + return Transform.translate(
  133 + offset: Offset(-offset, 0),
  134 + child: child,
  135 + );
  136 + },
  137 + child: OverflowBox(
  138 + alignment: Alignment.centerLeft,
  139 + minWidth: 0.0,
  140 + maxWidth: double.infinity,
  141 + minHeight: 0.0,
  142 + maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : null,
  143 + child: Row(
  144 + mainAxisSize: MainAxisSize.min,
  145 + children: List.generate(
  146 + repeatCount,
  147 + (_) => Padding(
  148 + padding: EdgeInsets.only(right: widget.blankSpace),
  149 + child: Text(
  150 + widget.text,
  151 + style: widget.style,
  152 + maxLines: 1,
  153 + ),
  154 + ),
  155 + ),
  156 + ),
  157 + ),
  158 + ),
  159 + );
  160 + },
  161 + );
  162 + }
  163 +}