36

Is there a way to smoothly animate a ScrollViewers vertical offset in Windows Phone 8.1 Runtime?

I have tried using the ScrollViewer.ChangeView() method and the change of vertical offset is not animated no matter if I set the disableAnimation parameter to true or false.

For example: myScrollViewer.ChangeView(null, myScrollViewer.VerticalOffset + p, null, false); The offset is changed without animation.

I also tried using a vertical offset mediator:

/// <summary>
/// Mediator that forwards Offset property changes on to a ScrollViewer
/// instance to enable the animation of Horizontal/VerticalOffset.
/// </summary>
public sealed class ScrollViewerOffsetMediator : FrameworkElement
{
    /// <summary>
    /// ScrollViewer instance to forward Offset changes on to.
    /// </summary>
    public ScrollViewer ScrollViewer
    {
        get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
        set { SetValue(ScrollViewerProperty, value); }
    }
    public static readonly DependencyProperty ScrollViewerProperty =
            DependencyProperty.Register("ScrollViewer",
            typeof(ScrollViewer),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(null, OnScrollViewerChanged));
    private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        var scrollViewer = (ScrollViewer)(e.NewValue);
        if (null != scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
        }
    }

    /// <summary>
    /// VerticalOffset property to forward to the ScrollViewer.
    /// </summary>
    public double VerticalOffset
    {
        get { return (double)GetValue(VerticalOffsetProperty); }
        set { SetValue(VerticalOffsetProperty, value); }
    }
    public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register("VerticalOffset",
            typeof(double),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(0.0, OnVerticalOffsetChanged));
    public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        if (null != mediator.ScrollViewer)
        {
            mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
        }
    }

    /// <summary>
    /// Multiplier for ScrollableHeight property to forward to the ScrollViewer.
    /// </summary>
    /// <remarks>
    /// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
    /// </remarks>
    public double ScrollableHeightMultiplier
    {
        get { return (double)GetValue(ScrollableHeightMultiplierProperty); }
        set { SetValue(ScrollableHeightMultiplierProperty, value); }
    }
    public static readonly DependencyProperty ScrollableHeightMultiplierProperty =
            DependencyProperty.Register("ScrollableHeightMultiplier",
            typeof(double),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(0.0, OnScrollableHeightMultiplierChanged));
    public static void OnScrollableHeightMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        var scrollViewer = mediator.ScrollViewer;
        if (null != scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
        }
    }
}

and I can animate the VerticalOffset property with DoubleAnimation:

Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
da.EnableDependentAnimation = true;
da.From = Mediator.ScrollViewer.VerticalOffset;
da.To = da.From + p;
da.Duration = new Duration(TimeSpan.FromMilliseconds(300));
da.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
Storyboard.SetTarget(da, Mediator);
Storyboard.SetTargetProperty(da, "(Mediator.VerticalOffset)");
sb.Children.Add(da);

sb.Begin();

Mediator is declared in XAML. But this animation is not smooth on my device (Lumia 930).

Cœur
  • 37,241
  • 25
  • 195
  • 267
Kristian Vukusic
  • 3,284
  • 6
  • 30
  • 46
  • 4
    One thing you could try is [`WinRTXamlToolkit`'s `ScrollToVerticalOffsetWithAnimation` extension](http://winrtxamltoolkit.codeplex.com/SourceControl/latest#WinRTXamlToolkit/WinRTXamlToolkit.Shared/Controls/Extensions/ScrollViewerExtensions.cs). You can implement it manually or add the library via Nuget. – Nate Diamond Oct 10 '14 at 01:27
  • 2
    I don't think, there is a way to make it completely smooth. This kind of animation probably isn't hardware accelerated. – Lukáš Neoproud May 22 '15 at 12:27
  • No... http://sviluppomobile.blogspot.fi/2013/04/smooth-scrolling-content-on-windows.html ? – Mikko Viitala Aug 11 '15 at 17:49
  • @MikkoVitala First of all with this method you lose virtualization, because you put a ListBox inside a ScrollViewer. this sample was for WP8.0, and I had implemented the same behavior too, but in WP8 this works smoothly because there is no concept of Dependent and Independent animations, all animations are independent and run on the composition thread. In WP8.1 dependent animations run on the UI thread and therefore you lose the smooth animation. But anyways, thanks for the link. – Kristian Vukusic Aug 11 '15 at 18:40
  • Still looking for an answer to this? –  Sep 25 '15 at 18:47
  • Don't have an answer myself, but felt like starting a bounty on this question to see if we can get it answered. –  Sep 28 '15 at 12:04
  • @Kirstian Vukusic you didn't say whether or not the first comment helped. What is the issue with WinRT extension? –  Sep 28 '15 at 21:15
  • Can you give me an example where `ChangeView` doesn't do the animation? It should animate regardless of whether virtualization is on or not. – Justin XL Sep 29 '15 at 10:29
  • In my project I am using the `ScrollViewer` of a `ListView` and it never animates. Maybe I have too complex DataTemplates, but I doubt it, because the scrolling is very smooth, just the programatical scrolling is not. When I use `ChangeView`, it just changes the vertical offset without animation. – Kristian Vukusic Sep 29 '15 at 11:05
  • Kristian, how about Justin's answer below? Did it work for you? I'd like to give him the bounty if his answer took care of this. –  Oct 02 '15 at 13:35
  • I'm sorry, I had a busy week... but it worked because of the Task.Delay which I didn't know would solve the problem. Anyways, thanks for starting the bounty which speed up obtaining the answer. – Kristian Vukusic Oct 02 '15 at 19:13

4 Answers4

15

You should stick with ChangeView for scrolling animations regardless of whether data virtualization is on or not.

Without seeing your code where the ChangeView doesn't work, it's a bit hard to guess what's really going on but there are a couple of things that you can try.

First approach is to add a Task.Delay(1) before calling ChangeView, just to give the OS some time to finish off other concurrent UI tasks.

await Task.Delay(1);
scrollViewer.ChangeView(null, scrollViewer.ScrollableHeight, null, false);

The second approach is a bit more complex. What I've noticed is that, when you have many complex items in the ListView, the scrolling animation from the first item to the last (from the ChangeView method) isn't very smooth at all.

This is because the ListView first needs to realize/render many items along the way due to data virtualization and then does the animated scrolling. Not very efficient IMHO.

What I came up with is this - First, use a non-animated ListView.ScrollIntoView to scroll to the last item just to get it realized. Then, call ChangeView to move the offset up to a size of the ActualHeight * 2 of the ListView with animation disabled (you can change it to whatever size you want based on your app's scrolling experience). Finally, call ChangeView again to scroll back to the end, with animation this time. Doing this will give a much better scrolling experience 'cause the scrolling distance is just the ActualHeight of the ListView.

Keep in mind that when the item you want to scroll to is already realized on the UI, you don't want to do anything above. You simply just calculate the distance between this item and the top of the ScrollViewer and call ChangeView to scroll to it.

I already wrapped the logic above in this answer's Update 2 section (thanks to this question I realized my initial answer doesn't work when virtualization is on :p). Let me know how you go.

Community
  • 1
  • 1
Justin XL
  • 38,763
  • 7
  • 88
  • 133
  • 2
    The first approach made the `ChangeView` smooth, because of the `Task.Delay`. I had no idea this would help so much. Thank you very much. – Kristian Vukusic Oct 02 '15 at 19:14
4

I think that question has already been answered here:

Animated (Smooth) scrolling on ScrollViewer

There is also the WinRT XAML Toolki, which provides "a way to scroll a ScrollViewer to specified offset with animation":

http://winrtxamltoolkit.codeplex.com/

Community
  • 1
  • 1
SalientGreen
  • 4,764
  • 3
  • 15
  • 20
  • 3
    When you look at the source of [ScrollViewerExtensions.cs](http://winrtxamltoolkit.codeplex.com/SourceControl/latest#WinRTXamlToolkit/WinRTXamlToolkit.Shared/Controls/Extensions/ScrollViewerExtensions.cs), the method which creates the animation is with the name `ScrollToVerticalOffsetWithAnimation`, and you can see the line `da.EnableDependentAnimation = true` which means that the animation is not run on the composition thread, which means that it may not run smooth, and this is the main question, how to animate the scrollviewer with **independent animations** – Kristian Vukusic Sep 29 '15 at 07:16
  • Neither of those are relevant to WinRT/Windows10 – Jerry Nixon Mar 01 '16 at 21:23
0

With ScrollToVerticalOffset deprecated/obsolete in newer builds of Windows 10 (leaving the ScrollViewOffSetMediator extension control no longer working), and the new ChangeView method not actually providing smooth or controllable animation, a new solution is needed. Please see my answer here which allows one to smoothly animate and zoom the ScrollViewer and its contents to any desired position, regardless of where the application's end user has the scrollbars initially positioned:

How to scroll to element in UWP

zax
  • 844
  • 8
  • 14
0

I believe this article is what you're looking for and it seems the method he used is working for you.

Quick Way:

  1. Add offset dependency parameter manually to scrollviewer.

  2. Duplicate your scrollviewer

  3. Use an animator.

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135