Is there a way to smoothly animate a ScrollViewer
s 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).
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/
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With