From f8226fcade0fd1e05cd85c59bafea91e5e8c6a3d Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Tue, 2 Sep 2025 18:33:33 +0800 Subject: [PATCH] opt marquee Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/marquee.dart | 99 +++++++++++-------- lib/pages/music/view.dart | 7 +- .../music/widget/music_video_card_h.dart | 3 +- lib/pages/video/widgets/header_control.dart | 58 +++++------ lib/plugin/pl_player/view.dart | 18 +++- 5 files changed, 112 insertions(+), 73 deletions(-) diff --git a/lib/common/widgets/marquee.dart b/lib/common/widgets/marquee.dart index 4cc0676b..78515899 100644 --- a/lib/common/widgets/marquee.dart +++ b/lib/common/widgets/marquee.dart @@ -3,47 +3,34 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; class MarqueeText extends StatelessWidget { - final double maxWidth; final String text; final TextStyle? style; final double spacing; final double velocity; + final MarqueeController? controller; const MarqueeText( this.text, { super.key, - required this.maxWidth, this.style, this.spacing = 0, this.velocity = 25, + this.controller, }); @override Widget build(BuildContext context) { - final textPainter = TextPainter( - text: TextSpan( - text: text, + return NormalMarquee( + velocity: velocity, + spacing: spacing, + controller: controller, + child: Text( + text, style: style, + maxLines: 1, + textDirection: TextDirection.ltr, ), - textDirection: TextDirection.ltr, - maxLines: 1, - )..layout(); - final width = textPainter.width; - final child = Text( - text, - style: style, - maxLines: 1, - textDirection: TextDirection.ltr, ); - if (width > maxWidth) { - return NormalMarquee( - velocity: velocity, - spacing: spacing, - child: child, - ); - } else { - return child; - } } } @@ -52,6 +39,7 @@ abstract class Marquee extends SingleChildRenderObjectWidget { final Clip clipBehavior; final double spacing; final double velocity; + final MarqueeController? controller; const Marquee({ super.key, @@ -60,6 +48,7 @@ abstract class Marquee extends SingleChildRenderObjectWidget { this.direction = Axis.horizontal, this.clipBehavior = Clip.hardEdge, this.spacing = 0, + this.controller, }); @override @@ -83,6 +72,7 @@ class NormalMarquee extends Marquee { super.direction, super.clipBehavior, super.spacing, + super.controller, }); @override @@ -91,6 +81,7 @@ class NormalMarquee extends Marquee { velocity: velocity, clipBehavior: clipBehavior, spacing: spacing, + controller: controller, ); } @@ -120,6 +111,7 @@ abstract class MarqueeRender extends RenderBox required double velocity, required double spacing, required this.clipBehavior, + this.controller, }) : _spacing = spacing, _velocity = velocity, _direction = direction, @@ -127,6 +119,8 @@ abstract class MarqueeRender extends RenderBox Clip clipBehavior; + MarqueeController? controller; + Axis _direction; Axis get direction => _direction; set direction(Axis value) { @@ -140,7 +134,7 @@ abstract class MarqueeRender extends RenderBox if (_velocity == value) return; _velocity = value; _simulation = _simulation?.copyWith(initialValue: _delta, velocity: value); - ticker?.reset(); + controller?.reset(); } double _spacing; @@ -155,7 +149,7 @@ abstract class MarqueeRender extends RenderBox addSize: value - _spacing, ); _spacing = value; - ticker?.reset(); + controller?.reset(); } double _delta = 0; @@ -167,27 +161,18 @@ abstract class MarqueeRender extends RenderBox @override void detach() { - ticker?.stop(); + controller?.dispose(); super.detach(); } - @override - void attach(PipelineOwner owner) { - super.attach(owner); - ticker?.start(); - } - @override void dispose() { - ticker?.dispose(); - ticker = null; + controller?.dispose(); super.dispose(); } late double _distance; - Ticker? ticker; - _MarqueeSimulation? _simulation; @override @@ -218,10 +203,11 @@ abstract class MarqueeRender extends RenderBox if (_distance > 0) { updateSize(); - ticker ??= Ticker(_onTick)..start(); + (controller ??= MarqueeController()) + ..ticker ??= Ticker(_onTick) + ..initStart(); } else { - ticker?.dispose(); - ticker = null; + controller?.dispose(); } } @@ -292,6 +278,7 @@ class _NormalMarqueeRender extends MarqueeRender { required super.velocity, required super.clipBehavior, required super.spacing, + super.controller, }); @override @@ -395,3 +382,37 @@ extension on Ticker { ..start(); } } + +class MarqueeController { + MarqueeController({this.autoStart = true}); + bool autoStart; + + Ticker? ticker; + + void initStart() { + if (autoStart) { + start(); + } + } + + void start() { + if (ticker != null) { + if (!ticker!.isTicking) { + ticker!.start(); + } + } + } + + void stop() { + ticker?.stop(); + } + + void reset() { + ticker?.reset(); + } + + void dispose() { + ticker?.dispose(); + ticker = null; + } +} diff --git a/lib/pages/music/view.dart b/lib/pages/music/view.dart index 918e1b86..b04c3da0 100644 --- a/lib/pages/music/view.dart +++ b/lib/pages/music/view.dart @@ -80,8 +80,9 @@ class _MusicDetailPageState extends CommonDynPageState { children: [ NetworkImgLayer( src: info.data.mvCover, - width: 40, - height: 40, + width: 36, + height: 36, + type: ImageType.avatar, ), Text(info.data.musicTitle!), ], @@ -456,7 +457,7 @@ class _MusicDetailPageState extends CommonDynPageState { behavior: HitTestBehavior.opaque, child: MarqueeText( item.musicTitle!, - maxWidth: maxWidth - 136, // 80 + 16 + 32 + 8 + spacing: 30, style: textTheme.titleMedium, ), ), diff --git a/lib/pages/music/widget/music_video_card_h.dart b/lib/pages/music/widget/music_video_card_h.dart index 4cb7d7c0..376b70b1 100644 --- a/lib/pages/music/widget/music_video_card_h.dart +++ b/lib/pages/music/widget/music_video_card_h.dart @@ -114,8 +114,9 @@ class MusicVideoCardH extends StatelessWidget { ], ), const SizedBox(height: 3), - BounceMarquee( + NormalMarquee( velocity: 25, + spacing: 30, child: Row( spacing: 8, children: [ diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index c782299b..afcc4415 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -83,6 +83,9 @@ class HeaderControlState extends TripleState { Timer? clock; bool get isFullScreen => plPlayerController.isFullScreen.value; Box setting = GStorage.setting; + MarqueeController? marqueeController; + MarqueeController get _marqueeController => + marqueeController ??= MarqueeController(autoStart: false); @override void initState() { @@ -97,6 +100,8 @@ class HeaderControlState extends TripleState { @override void dispose() { clock?.cancel(); + marqueeController?.dispose(); + marqueeController = null; super.dispose(); } @@ -1911,35 +1916,30 @@ class HeaderControlState extends TripleState { padding: isPortrait ? EdgeInsets.zero : const EdgeInsets.only(right: 10), - child: LayoutBuilder( - builder: (context, constraints) { - return Obx( - () { - final videoDetail = - introController.videoDetail.value; - final String title; - if (videoDetail.videos == 1) { - title = videoDetail.title!; - } else { - title = - videoDetail.pages - ?.firstWhereOrNull( - (e) => - e.cid == videoDetailCtr.cid.value, - ) - ?.pagePart ?? - videoDetail.title!; - } - return MarqueeText( - title, - maxWidth: constraints.maxWidth, - spacing: 30, - style: const TextStyle( - color: Colors.white, - fontSize: 16, - ), - ); - }, + child: Obx( + () { + final videoDetail = introController.videoDetail.value; + final String title; + if (videoDetail.videos == 1) { + title = videoDetail.title!; + } else { + title = + videoDetail.pages + ?.firstWhereOrNull( + (e) => e.cid == videoDetailCtr.cid.value, + ) + ?.pagePart ?? + videoDetail.title!; + } + return MarqueeText( + title, + spacing: 30, + velocity: 30, + controller: _marqueeController, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), ); }, ), diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index a3f6ac2f..8e932c3c 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -176,7 +176,23 @@ class _PLVideoPlayerState extends State super.initState(); _controlsListener = plPlayerController.showControls.listen((bool val) { final visible = val && !plPlayerController.controlsLock.value; - visible ? animationController.forward() : animationController.reverse(); + if (visible) { + animationController.forward(); + widget + .videoDetailController + ?.headerCtrKey + .currentState + ?.marqueeController + ?.start(); + } else { + animationController.reverse(); + widget + .videoDetailController + ?.headerCtrKey + .currentState + ?.marqueeController + ?.stop(); + } }); animationController = AnimationController( vsync: this,