【问题标题】:UWP Scroll Text from end to startUWP 从头到尾滚动文本
【发布时间】: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。如果我没有很好地解释我的问题,您可能需要检查一下。

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

【问题讨论】:

    标签: c# xaml uwp win-universal-app


    【解决方案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();
        }
    

    【讨论】:

      猜你喜欢
      • 2020-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多