UWP 从头到尾滚动文本

UWP Scroll Text from end to start

我正在实现一个滚动文本,当指针进入它时,它开始滚动其内容。

我可以使用下面的代码让它滚动:

private DispatcherTimer ScrollingTextTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(16) };
ScrollingTextTimer.Tick += (sender, e) =>
{
    MainTitleScrollViewer.ChangeView(MainTitleScrollViewer.HorizontalOffset + 3, null, null);
    if (MainTitleScrollViewer.HorizontalOffset == MainTitleScrollViewer.ScrollableWidth)
    {
        MainTitleScrollViewer.ChangeView(0, null, null);
        ScrollingTextTimer.Stop();
    }
};

XAML:

<ScrollViewer
    x:Name="MainTitleScrollViewer"
    Grid.Row="0"
    Grid.Column="1"
    Margin="10,5"
    HorizontalScrollBarVisibility="Hidden"
    VerticalScrollBarVisibility="Disabled">
    <TextBlock
        x:Name="MainTitleTextBlock"
        VerticalAlignment="Bottom"
        FontSize="24"
        Foreground="White" />
</ScrollViewer>

但是,我还想实现一个附加功能。当文本滚动到末尾时,我不希望它滚动回开头。我希望它一直滚动到开头。您可以从我在下面发布的屏幕截图中看到我的意思。截图来自 Groove Music。如果我没有很好地解释我的问题,你可能需要检查一下。

一个可能的解决方案是将文本加倍并在它们之间放置一些 space。但是我不知道什么时候停止滚动。

这种跑马灯效果推荐使用Storyboard。定时器可能因时间间隔原因造成缺失

这里有一个完整的demo,希望对你有所帮助。

xaml

<Grid>
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="Gray" BorderThickness="1" Padding="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Source="ms-appx:///Assets/StoreLogo.png" Width="100" Height="100" VerticalAlignment="Center"/>
        <StackPanel Grid.Column="1" Margin="20,0,0,0" VerticalAlignment="Center">
            <ScrollViewer Width="200" 
                          PointerEntered="ScrollViewer_PointerEntered" 
                          HorizontalScrollBarVisibility="Hidden" 
                          VerticalScrollBarVisibility="Hidden" 
                          PointerExited="ScrollViewer_PointerExited">
                <TextBlock FontSize="25" x:Name="TitleBlock">
                    <TextBlock.RenderTransform>
                        <TranslateTransform X="0"/>
                    </TextBlock.RenderTransform>
                </TextBlock>
            </ScrollViewer>

            <TextBlock FontSize="20" FontWeight="Bold" Text="Gotye" Margin="0,10,0,0"/>
        </StackPanel>
    </Grid>
</Grid>

xaml.cs

Storyboard _scrollAnimation;
public ScrollTextPage()
{
    this.InitializeComponent();
    string text = "How about you?";
    TitleBlock.Text = text + "    " + text;
}

private void ScrollViewer_PointerEntered(object sender, PointerRoutedEventArgs e)
{
    AnimationInit();
    _scrollAnimation.Begin();
}

private void ScrollViewer_PointerExited(object sender, PointerRoutedEventArgs e)
{
    _scrollAnimation.Stop();
}
public void AnimationInit()
{
    _scrollAnimation = new Storyboard();
    var animation = new DoubleAnimation();
    animation.Duration = TimeSpan.FromSeconds(5);
    animation.RepeatBehavior = new RepeatBehavior(1);
    animation.From = 0;
    // Here you need to calculate based on the number of spaces and the current FontSize
    animation.To = -((TitleBlock.ActualWidth/2)+13);
    Storyboard.SetTarget(animation, TitleBlock);
    Storyboard.SetTargetProperty(animation, "(UIElement.RenderTransform).(TranslateTransform.X)");
    _scrollAnimation.Children.Add(animation);
}

简单来说,水平滚动TextBlock比滚动ScrollViewer更可控。

思路和你的差不多,采用字符串拼接的方式实现无缝滚动,通过当前字号计算出space的宽度,从而准确滚动到第二行开头字符串.

此致。

这是我的做法,源代码是here(xaml) and here(csharp):

我创建了一个名为 ScrollingTextBlockUserControl

这是UserControl的XAML内容。

<Grid>
    <ScrollViewer x:Name="TextScrollViewer">
        <TextBlock x:Name="NormalTextBlock" />
    </ScrollViewer>
    <ScrollViewer x:Name="RealScrollViewer">
        <TextBlock x:Name="ScrollTextBlock" Visibility="Collapsed" />
    </ScrollViewer>
</Grid>

基本上,您需要两个 ScrollViewer 重叠。

第一个ScrollViewer用于检测文本是否可滚动。而里面的TextBlock是放正文的

第二个ScrollViewer才是真正的ScrollViewer。您将滚动这个而不是第一个。其中的 TextBlockText 等于

ScrollTextBlock.Text = NormalTextBlock.Text + new string(' ', 10) + NormalTextBlock.Text

new string(' ', 10) 只是一些空白 space 使您的文本看起来不紧密连接,您可以从问题中的图像中看到这一点。你可以把它改成任何你想要的。

然后在你需要的csharp代码中(解释在评论里):

    // Using 16ms because 60Hz is already good for human eyes.
    private readonly DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(16) };

    public ScrollingTextBlock()
    {
        this.InitializeComponent();
        timer.Tick += (sender, e) =>
        {
            // Calculate the total offset to scroll. It is fixed after your text is set.
            // Since we need to scroll to the "start" of the text,
            // the offset is equal the length of your text plus the length of the space,
            // which is the difference of the ActualWidth of the two TextBlocks.
            double offset = ScrollTextBlock.ActualWidth - NormalTextBlock.ActualWidth;
            // Scroll it horizontally.
            // Notice the Math.Min here. You cannot scroll more than offset.
            // " + 2" is just the distance it advances,
            // meaning that it also controls the speed of the animation.
            RealScrollViewer.ChangeView(Math.Min(RealScrollViewer.HorizontalOffset + 2, offset), null, null);
            // If scroll to the offset
            if (RealScrollViewer.HorizontalOffset == offset)
            {
                // Re-display the NormalTextBlock first so that the text won't blink because they overlap.
                NormalTextBlock.Visibility = Visibility.Visible;
                // Hide the ScrollTextBlock.
                // Hiding it will also set the HorizontalOffset of RealScrollViewer to 0,
                // so that RealScrollViewer will be scrolling from the beginning of ScrollTextBlock next time.
                ScrollTextBlock.Visibility = Visibility.Collapsed;
                // Stop the animation/ticking.
                timer.Stop();
            }
        };
    }

    public void StartScrolling()
    {
        // Checking timer.IsEnabled is to avoid restarting the animation when the text is already scrolling.
        // IsEnabled is true if timer has started, false if timer is stopped.
        // Checking TextScrollViewer.ScrollableWidth is for making sure the text is scrollable.
        if (timer.IsEnabled || TextScrollViewer.ScrollableWidth == 0) return;
        // Display this first so that user won't feel NormalTextBlock will be hidden.
        ScrollTextBlock.Visibility = Visibility.Visible;
        // Hide the NormalTextBlock so that it won't overlap with ScrollTextBlock when scrolling.
        NormalTextBlock.Visibility = Visibility.Collapsed;
        // Start the animation/ticking.
        timer.Start();
    }