Animating the scrolling in a ScrollViewer seems to be a common task. I implemented it using a timer, similar to the approach found here. This method was working great, it was very smooth and looked perfect.
However, now that the complexity and number of objects contained within my ScrollViewer has increased, the animation looks very jerky. I find this odd because it works fine if I scroll manually.
public void ShiftLeft(int speed = 11)
{
CustomTimer timer = new CustomTimer(); //DispatchTimer with "life"
timer.Interval = new TimeSpan(0, 0, 0, 0, 5);
timer.Tick += ((sender, e) =>
{
scrollViewer1.ScrollToHorizontalOffset(
scrollViewer1.HorizontalOffset - (scrollViewer1.ScrollableWidth / (gridColumnCount - 3) / speed));
if (scrollViewer1.HorizontalOffset == 0) //cant scroll any more
((CustomTimer)sender).Stop();
((CustomTimer)sender).life++;
if (((CustomTimer)sender).life >= speed) //reached destination
((CustomTimer)sender).Stop();
});
timer.Start();
}
Is there a problem with my approach that is causing this weird jerking? Any idea how to fix it?
CompositionTarget.Rendering will be a better fit for animating things as it fires every time a frame is about to be rendered. Try something like this instead:
public void Shift(ScrollViewer target, double speed = 11, double distance = 20)
{
double startOffset = target.HorizontalOffset;
double destinationOffset = target.HorizontalOffset + distance;
if (destinationOffset < 0)
{
destinationOffset = 0;
distance = target.HorizontalOffset;
}
if (destinationOffset > target.ScrollableWidth)
{
destinationOffset = target.ScrollableWidth;
distance = target.ScrollableWidth - target.HorizontalOffset;
}
double animationTime = distance / speed;
DateTime startTime = DateTime.Now;
EventHandler renderHandler = null;
renderHandler = (sender, args) =>
{
double elapsed = (DateTime.Now - startTime).TotalSeconds;
if (elapsed >= animationTime)
{
target.ScrollToHorizontalOffset(destinationOffset);
CompositionTarget.Rendering -= renderHandler;
}
target.ScrollToHorizontalOffset(startOffset + (elapsed * speed));
};
CompositionTarget.Rendering += renderHandler;
}
EDIT: added range checking
Use negative distance values to scroll left.
EDIT 2:
You might want to use this CompositionTargetEx implementation instead of CompositionTarget, as it will only fire when a new frame will actually be drawn by the render thread:
https://stackoverflow.com/a/16334423/612510
EDIT 3:
Since you are on WPF (and not Silverlight, like I am more accustomed to) you might use the Stopwatch class to measure elapsed seconds instead of my DateTime.Now method.
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