You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI中如何实现带淡入淡出效果的滚动字幕Label?

适配动态背景的.NET MAUI滚动字幕淡入淡出实现方案

针对流媒体音乐播放器中滚动字幕(Marquee)在动态模糊专辑封面背景下无法用纯色遮罩实现淡入淡出的问题,推荐以下两种跨平台、不依赖背景色的解决方案:

方案一:利用OpacityMask结合渐变动画(推荐)

核心思路是通过OpacityMask将渐变透明度直接作用于Label本身,无需依赖背景色,完美适配动态模糊背景。

步骤1:XAML配置Label及OpacityMask

<!-- 父容器需固定宽度,用于计算滚动范围 -->
<ContentView x:Name="MarqueeContainer" WidthRequest="300" HeightRequest="40">
    <Label x:Name="MarqueeLabel" 
           Text="超长的流媒体音乐标题需要滚动显示" 
           HorizontalOptions="Start"
           VerticalTextAlignment="Center">
        <Label.OpacityMask>
            <!-- 定义左右透明、中间不透明的线性渐变 -->
            <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
                <GradientStop x:Name="LeftTransparentStop" Color="#00000000" Offset="0" />
                <GradientStop x:Name="LeftOpaqueStop" Color="#FFFFFFFF" Offset="0.1" />
                <GradientStop x:Name="RightOpaqueStop" Color="#FFFFFFFF" Offset="0.9" />
                <GradientStop x:Name="RightTransparentStop" Color="#00000000" Offset="1" />
            </LinearGradientBrush>
        </Label.OpacityMask>
    </Label>
</ContentView>

步骤2:C#控制滚动与淡入淡出动画

结合已有的TranslationX滚动逻辑,同步调整渐变停止点的位置实现动画:

using Microsoft.Maui.Controls.Shapes;
using CommunityToolkit.Maui.Animations; // 需安装CommunityToolkit.Maui

async void StartMarqueeAnimation()
{
    var labelWidth = MarqueeLabel.Measure(double.PositiveInfinity, double.PositiveInfinity).Request.Width;
    var containerWidth = MarqueeContainer.Width;

    // 标题未超出容器宽度时无需滚动
    if (labelWidth <= containerWidth) return;

    while (true)
    {
        // 重置初始位置(Label在容器右侧外)
        MarqueeLabel.TranslationX = containerWidth;
        ResetGradientStops();

        // 淡入动画:Label从右侧进入,同时渐变透明区域收缩
        await Task.WhenAll(
            LeftTransparentStop.OffsetTo(-0.1, 200),
            LeftOpaqueStop.OffsetTo(0, 200),
            MarqueeLabel.TranslateTo(0, 0, 200)
        );

        // 匀速滚动到左侧即将离开容器
        var scrollDistance = labelWidth - containerWidth;
        await MarqueeLabel.TranslateTo(-scrollDistance, 0, (uint)(scrollDistance * 20)); // 20为速度系数,可调整

        // 淡出动画:Label离开容器,同时渐变透明区域扩展
        await Task.WhenAll(
            RightOpaqueStop.OffsetTo(1, 200),
            RightTransparentStop.OffsetTo(1.1, 200),
            MarqueeLabel.TranslateTo(-labelWidth, 0, 200)
        );

        // 停顿后循环
        await Task.Delay(1000);
    }
}

void ResetGradientStops()
{
    LeftTransparentStop.Offset = 0;
    LeftOpaqueStop.Offset = 0.1;
    RightOpaqueStop.Offset = 0.9;
    RightTransparentStop.Offset = 1;
}

方案二:SkiaSharp自定义绘制(灵活性更高)

通过SkiaSharp直接绘制带渐变透明边缘的文本,滚动时自然呈现淡入淡出效果,完全不受背景影响。

步骤1:创建自定义MarqueeLabel

using SkiaSharp;
using SkiaSharp.Views.Maui;

public class SkiaMarqueeLabel : SKCanvasView
{
    public static readonly BindableProperty TextProperty = BindableProperty.Create(
        nameof(Text), typeof(string), typeof(SkiaMarqueeLabel), string.Empty);

    public static readonly BindableProperty FontSizeProperty = BindableProperty.Create(
        nameof(FontSize), typeof(double), typeof(SkiaMarqueeLabel), 14.0);

    public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
        nameof(TextColor), typeof(Color), typeof(SkiaMarqueeLabel), Colors.White);

    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    public double FontSize
    {
        get => (double)GetValue(FontSizeProperty);
        set => SetValue(FontSizeProperty, value);
    }

    public Color TextColor
    {
        get => (Color)GetValue(TextColorProperty);
        set => SetValue(TextColorProperty, value);
    }

    public double TranslationX { get; set; } = 0;

    protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
    {
        base.OnPaintSurface(args);
        var canvas = args.Surface.Canvas;
        var info = args.Info;
        canvas.Clear();

        if (string.IsNullOrEmpty(Text)) return;

        var paint = new SKPaint
        {
            Color = TextColor.ToSKColor(),
            TextSize = (float)FontSize,
            IsAntialias = true,
            TextAlign = SKTextAlign.Left
        };

        // 创建文本边缘渐变Shader:左右透明,中间不透明
        var gradientColors = new[] 
        { 
            SKColors.Transparent, 
            TextColor.ToSKColor(), 
            TextColor.ToSKColor(), 
            SKColors.Transparent 
        };
        var gradientOffsets = new float[] { 0, 0.1f, 0.9f, 1 };
        var shader = SKShader.CreateLinearGradient(
            new SKPoint(0, info.Height / 2),
            new SKPoint(info.Width, info.Height / 2),
            gradientColors,
            gradientOffsets,
            SKShaderTileMode.Clamp
        );
        paint.Shader = shader;

        // 计算文本绘制位置
        var textY = info.Height / 2 + paint.TextSize / 2 - paint.FontMetrics.Descent;
        canvas.DrawText(Text, (float)TranslationX, textY, paint);
    }
}

步骤2:使用自定义控件并控制滚动

<local:SkiaMarqueeLabel x:Name="SkiaMarquee" 
                        Text="超长的流媒体音乐标题需要滚动显示"
                        FontSize="16"
                        TextColor="White"
                        WidthRequest="300"
                        HeightRequest="40" />

C#动画逻辑和方案一类似,只需控制TranslationX并调用InvalidateSurface()刷新绘制:

async void StartSkiaMarquee()
{
    var paint = new SKPaint { TextSize = 16 };
    var textWidth = paint.MeasureText(SkiaMarquee.Text);
    var containerWidth = SkiaMarquee.Width;

    if (textWidth <= containerWidth) return;

    while (true)
    {
        SkiaMarquee.TranslationX = containerWidth;
        SkiaMarquee.InvalidateSurface();

        // 淡入滚动
        await Task.Run(async () =>
        {
            while (SkiaMarquee.TranslationX > 0)
            {
                SkiaMarquee.TranslationX -= 1;
                SkiaMarquee.InvalidateSurface();
                await Task.Delay(16);
            }
        });

        // 匀速滚动
        await Task.Run(async () =>
        {
            var targetX = -(textWidth - containerWidth);
            while (SkiaMarquee.TranslationX > targetX)
            {
                SkiaMarquee.TranslationX -= 1;
                SkiaMarquee.InvalidateSurface();
                await Task.Delay(16);
            }
        });

        // 淡出滚动
        await Task.Run(async () =>
        {
            while (SkiaMarquee.TranslationX > -textWidth)
            {
                SkiaMarquee.TranslationX -= 1;
                SkiaMarquee.InvalidateSurface();
                await Task.Delay(16);
            }
        });

        await Task.Delay(1000);
    }
}

内容的提问来源于stack exchange,提问作者Mephisztoe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 18:05:38