Commit cf47b7139673f4e8e213a23b7276e3aa55c6d5bb

Authored by 常守达
1 parent 526cc826

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

... ... @@ -3,6 +3,7 @@ import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart';
import 'package:doublefeel_flutter/app/modules/home/controllers/my_controller.dart';
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/app/utils/dialog_utils.dart';
import 'package:doublefeel_flutter/app/widget/marquee_text.dart';
import 'package:doublefeel_flutter/core/network/api/user_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
... ... @@ -417,13 +418,9 @@ class _DeleteAccountBottomSheet extends StatelessWidget {
children: [
Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 72),
child: Text(
AppLocalizations.of(context)!
.areYouSureYouWantToDeleteYourAccount,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
padding: const EdgeInsets.symmetric(horizontal: 54),
child: MarqueeText(
text: context.l10n.areYouSureYouWantToDeleteYourAccount,
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 16,
... ...
import 'dart:async';
import 'package:flutter/material.dart';
/// 无缝首尾相接的跑马灯文字组件:
/// - 当文字宽度 <= 容器宽度时:正常居中/按 textAlign 静止展示
/// - 当文字宽度 > 容器宽度时:文字向左平滑连续滚动,尾部自带留白并首尾无缝衔接循环
class MarqueeText extends StatefulWidget {
const MarqueeText({
super.key,
required this.text,
this.style,
this.textAlign = TextAlign.center,
this.velocity = 30.0,
this.blankSpace = 48.0,
this.startDelay = const Duration(seconds: 1),
});
final String text;
final TextStyle? style;
final TextAlign textAlign;
/// 滚动速度(像素/秒),数值越大滚动越快,建议 25~40
final double velocity;
/// 首尾循环之间的间隔空白宽度(像素)
final double blankSpace;
/// 页面加载后首次滚动的初始延迟
final Duration startDelay;
@override
State<MarqueeText> createState() => _MarqueeTextState();
}
class _MarqueeTextState extends State<MarqueeText>
with SingleTickerProviderStateMixin {
AnimationController? _controller;
double _lastTextWidth = 0.0;
Timer? _delayTimer;
@override
void dispose() {
_delayTimer?.cancel();
_controller?.dispose();
super.dispose();
}
void _setupAnimation(double textWidth, double containerWidth) {
if (textWidth <= containerWidth || containerWidth <= 0) {
_delayTimer?.cancel();
_controller?.stop();
_controller?.dispose();
_controller = null;
_lastTextWidth = 0;
return;
}
final cycleDistance = textWidth + widget.blankSpace;
if (_controller != null && (_lastTextWidth - textWidth).abs() < 1.0) {
return;
}
_lastTextWidth = textWidth;
_delayTimer?.cancel();
_controller?.dispose();
final durationSeconds = cycleDistance / widget.velocity;
final totalDuration = Duration(
milliseconds: (durationSeconds * 1000).toInt().clamp(1000, 60000),
);
final controller = AnimationController(
vsync: this,
duration: totalDuration,
);
_controller = controller;
_delayTimer = Timer(widget.startDelay, () {
if (mounted && _controller == controller) {
controller.repeat();
}
});
}
@override
Widget build(BuildContext context) {
final effectiveStyle =
DefaultTextStyle.of(context).style.merge(widget.style);
return LayoutBuilder(
builder: (context, constraints) {
final textPainter = TextPainter(
text: TextSpan(text: widget.text, style: effectiveStyle),
textDirection: Directionality.of(context),
maxLines: 1,
)..layout();
final textWidth = textPainter.width;
final maxWidth = constraints.maxWidth;
// 文本未超出容器宽度:普通渲染
if (textWidth <= maxWidth || maxWidth.isInfinite) {
_setupAnimation(0, 0);
return Text(
widget.text,
style: widget.style,
textAlign: widget.textAlign,
maxLines: 1,
);
}
// 文本超出容器宽度:开启首尾无缝循环跑马灯
_setupAnimation(textWidth, maxWidth);
final cycleDistance = textWidth + widget.blankSpace;
final repeatCount = (maxWidth / cycleDistance).ceil() + 2;
final controller = _controller;
if (controller == null) {
return Text(
widget.text,
style: widget.style,
maxLines: 1,
);
}
return ClipRect(
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
final offset = controller.value * cycleDistance;
return Transform.translate(
offset: Offset(-offset, 0),
child: child,
);
},
child: OverflowBox(
alignment: Alignment.centerLeft,
minWidth: 0.0,
maxWidth: double.infinity,
minHeight: 0.0,
maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : null,
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
repeatCount,
(_) => Padding(
padding: EdgeInsets.only(right: widget.blankSpace),
child: Text(
widget.text,
style: widget.style,
maxLines: 1,
),
),
),
),
),
),
);
},
);
}
}
... ...