opt marquee

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-09-02 18:33:33 +08:00
parent 498ab2818e
commit f8226fcade
5 changed files with 112 additions and 73 deletions

View File

@@ -3,47 +3,34 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
class MarqueeText extends StatelessWidget { class MarqueeText extends StatelessWidget {
final double maxWidth;
final String text; final String text;
final TextStyle? style; final TextStyle? style;
final double spacing; final double spacing;
final double velocity; final double velocity;
final MarqueeController? controller;
const MarqueeText( const MarqueeText(
this.text, { this.text, {
super.key, super.key,
required this.maxWidth,
this.style, this.style,
this.spacing = 0, this.spacing = 0,
this.velocity = 25, this.velocity = 25,
this.controller,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textPainter = TextPainter( return NormalMarquee(
text: TextSpan( velocity: velocity,
text: text, spacing: spacing,
controller: controller,
child: Text(
text,
style: style, 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 Clip clipBehavior;
final double spacing; final double spacing;
final double velocity; final double velocity;
final MarqueeController? controller;
const Marquee({ const Marquee({
super.key, super.key,
@@ -60,6 +48,7 @@ abstract class Marquee extends SingleChildRenderObjectWidget {
this.direction = Axis.horizontal, this.direction = Axis.horizontal,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.spacing = 0, this.spacing = 0,
this.controller,
}); });
@override @override
@@ -83,6 +72,7 @@ class NormalMarquee extends Marquee {
super.direction, super.direction,
super.clipBehavior, super.clipBehavior,
super.spacing, super.spacing,
super.controller,
}); });
@override @override
@@ -91,6 +81,7 @@ class NormalMarquee extends Marquee {
velocity: velocity, velocity: velocity,
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
spacing: spacing, spacing: spacing,
controller: controller,
); );
} }
@@ -120,6 +111,7 @@ abstract class MarqueeRender extends RenderBox
required double velocity, required double velocity,
required double spacing, required double spacing,
required this.clipBehavior, required this.clipBehavior,
this.controller,
}) : _spacing = spacing, }) : _spacing = spacing,
_velocity = velocity, _velocity = velocity,
_direction = direction, _direction = direction,
@@ -127,6 +119,8 @@ abstract class MarqueeRender extends RenderBox
Clip clipBehavior; Clip clipBehavior;
MarqueeController? controller;
Axis _direction; Axis _direction;
Axis get direction => _direction; Axis get direction => _direction;
set direction(Axis value) { set direction(Axis value) {
@@ -140,7 +134,7 @@ abstract class MarqueeRender extends RenderBox
if (_velocity == value) return; if (_velocity == value) return;
_velocity = value; _velocity = value;
_simulation = _simulation?.copyWith(initialValue: _delta, velocity: value); _simulation = _simulation?.copyWith(initialValue: _delta, velocity: value);
ticker?.reset(); controller?.reset();
} }
double _spacing; double _spacing;
@@ -155,7 +149,7 @@ abstract class MarqueeRender extends RenderBox
addSize: value - _spacing, addSize: value - _spacing,
); );
_spacing = value; _spacing = value;
ticker?.reset(); controller?.reset();
} }
double _delta = 0; double _delta = 0;
@@ -167,27 +161,18 @@ abstract class MarqueeRender extends RenderBox
@override @override
void detach() { void detach() {
ticker?.stop(); controller?.dispose();
super.detach(); super.detach();
} }
@override
void attach(PipelineOwner owner) {
super.attach(owner);
ticker?.start();
}
@override @override
void dispose() { void dispose() {
ticker?.dispose(); controller?.dispose();
ticker = null;
super.dispose(); super.dispose();
} }
late double _distance; late double _distance;
Ticker? ticker;
_MarqueeSimulation? _simulation; _MarqueeSimulation? _simulation;
@override @override
@@ -218,10 +203,11 @@ abstract class MarqueeRender extends RenderBox
if (_distance > 0) { if (_distance > 0) {
updateSize(); updateSize();
ticker ??= Ticker(_onTick)..start(); (controller ??= MarqueeController())
..ticker ??= Ticker(_onTick)
..initStart();
} else { } else {
ticker?.dispose(); controller?.dispose();
ticker = null;
} }
} }
@@ -292,6 +278,7 @@ class _NormalMarqueeRender extends MarqueeRender {
required super.velocity, required super.velocity,
required super.clipBehavior, required super.clipBehavior,
required super.spacing, required super.spacing,
super.controller,
}); });
@override @override
@@ -395,3 +382,37 @@ extension on Ticker {
..start(); ..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;
}
}

View File

@@ -80,8 +80,9 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: info.data.mvCover, src: info.data.mvCover,
width: 40, width: 36,
height: 40, height: 36,
type: ImageType.avatar,
), ),
Text(info.data.musicTitle!), Text(info.data.musicTitle!),
], ],
@@ -456,7 +457,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: MarqueeText( child: MarqueeText(
item.musicTitle!, item.musicTitle!,
maxWidth: maxWidth - 136, // 80 + 16 + 32 + 8 spacing: 30,
style: textTheme.titleMedium, style: textTheme.titleMedium,
), ),
), ),

View File

@@ -114,8 +114,9 @@ class MusicVideoCardH extends StatelessWidget {
], ],
), ),
const SizedBox(height: 3), const SizedBox(height: 3),
BounceMarquee( NormalMarquee(
velocity: 25, velocity: 25,
spacing: 30,
child: Row( child: Row(
spacing: 8, spacing: 8,
children: [ children: [

View File

@@ -83,6 +83,9 @@ class HeaderControlState extends TripleState<HeaderControl> {
Timer? clock; Timer? clock;
bool get isFullScreen => plPlayerController.isFullScreen.value; bool get isFullScreen => plPlayerController.isFullScreen.value;
Box setting = GStorage.setting; Box setting = GStorage.setting;
MarqueeController? marqueeController;
MarqueeController get _marqueeController =>
marqueeController ??= MarqueeController(autoStart: false);
@override @override
void initState() { void initState() {
@@ -97,6 +100,8 @@ class HeaderControlState extends TripleState<HeaderControl> {
@override @override
void dispose() { void dispose() {
clock?.cancel(); clock?.cancel();
marqueeController?.dispose();
marqueeController = null;
super.dispose(); super.dispose();
} }
@@ -1911,35 +1916,30 @@ class HeaderControlState extends TripleState<HeaderControl> {
padding: isPortrait padding: isPortrait
? EdgeInsets.zero ? EdgeInsets.zero
: const EdgeInsets.only(right: 10), : const EdgeInsets.only(right: 10),
child: LayoutBuilder( child: Obx(
builder: (context, constraints) { () {
return Obx( final videoDetail = introController.videoDetail.value;
() { final String title;
final videoDetail = if (videoDetail.videos == 1) {
introController.videoDetail.value; title = videoDetail.title!;
final String title; } else {
if (videoDetail.videos == 1) { title =
title = videoDetail.title!; videoDetail.pages
} else { ?.firstWhereOrNull(
title = (e) => e.cid == videoDetailCtr.cid.value,
videoDetail.pages )
?.firstWhereOrNull( ?.pagePart ??
(e) => videoDetail.title!;
e.cid == videoDetailCtr.cid.value, }
) return MarqueeText(
?.pagePart ?? title,
videoDetail.title!; spacing: 30,
} velocity: 30,
return MarqueeText( controller: _marqueeController,
title, style: const TextStyle(
maxWidth: constraints.maxWidth, color: Colors.white,
spacing: 30, fontSize: 16,
style: const TextStyle( ),
color: Colors.white,
fontSize: 16,
),
);
},
); );
}, },
), ),

View File

@@ -176,7 +176,23 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
super.initState(); super.initState();
_controlsListener = plPlayerController.showControls.listen((bool val) { _controlsListener = plPlayerController.showControls.listen((bool val) {
final visible = val && !plPlayerController.controlsLock.value; 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( animationController = AnimationController(
vsync: this, vsync: this,