UWP 从头到尾滚动文本

Posted

技术标签:

【中文标题】UWP 从头到尾滚动文本【英文标题】:UWP Scroll Text from end to start 【发布时间】:2020-01-24 09:55:14 【问题描述】:

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

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

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。如果我没有很好地解释我的问题,您可能需要检查一下。

一个可能的解决方案可能是将文本加倍并在它们之间放置一些空间。但如果是这样,我不知道何时停止滚动。

【问题讨论】:

【参考方案1】:

这种跑马灯的效果推荐使用Storyboard。由于时间间隔,计时器可能会导致缺少。

这是一个完整的演示,希望对你有所帮助。

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 更可控。

思路和你的差不多,使用字符串拼接的方式实现无缝滚动,通过当前字体大小计算空间宽度,从而准确滚动到第二个字符串的开头。

最好的问候。

【讨论】:

如何根据字体大小计算空间宽度? 这取决于字体。如果是等宽字体,则空间为 1/2 字体大小。如果不是,则需要对其进行测试。没有合适的算法来确定它。你需要自己调整。 如何知道动画是否播放完毕?预期的效果是,当指针进入时,动画开始。无论指针退出事件和指针重新输入事件如何,它都会一直播放动画,直到文本完成滚动。 而且我认为不会缺少,因为我的Interval 是 16 毫秒,大约是 60 赫兹。这对人的眼睛有好处。 Animation Duration 对我来说不好,因为如果文本很长,那么滚动会很快。 注意animationFromTo 属性,它们标记了起点和终点。同时还可以收听_scrollAnimation.Completed事件,这也是我使用Storyboard的原因。【参考方案2】:

这是我的做法,源代码是here(xaml)和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>

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

第一个ScrollViewer 用于检测文本是否可滚动。其中的TextBlock 用于放置文本。

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

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

new string(' ', 10) 只是一些空白,使您的文本看起来不紧密连接,您可以从问题中的图像中看到。您可以将其更改为您想要的任何内容。

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

    // 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();
    

【讨论】:

以上是关于UWP 从头到尾滚动文本的主要内容,如果未能解决你的问题,请参考以下文章

UWP 独立滚动视图

如何滚动到 UWP 中的元素

SHIFT +滚动到ScrollViewer UWP中水平滚动

UWP:可滚动的PivotHeader

UWP 滚动条私人定制

2019-11-29-win10-uwp-如何判断一个控件在滚动条的里面是用户可见