1. First Look at the Effect
2. Principle
Although the effect is very simple, some of the information on the Internet involves a considerable amount of code, and the effect is not very ideal, scrolling without a smooth feeling. I provide a total of no more than 130 lines of source code, can achieve the effect of the above.
Essentially, we just have to take over the scrolling logic of ScrollViewer
and replace that logic with inertia, so how do we take over? The key here is Shielding ScrollViewer
's mouse wheel event first:
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
e.Handled = true;
}
So that ScrollViewer
won't respond to mouse wheel event, we're going to be here to make a story. First, we add a property IsEnableInertia
to this ScrollViewer
to control whether to use inertia. Different strokes for different folks. Don't try to force everyone to use inertia, so the wheel response method becomes:
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
if (!IsEnableInertia)
{
base.OnMouseWheel(e);
return;
}
e.Handled = true;
}
You can use ScrollViewer.ScrollToVerticalOffset
to control the vertical scrolling of the ScrollViewer
, as well as horizontally. Why not use VerticalOffset
? Because when VerticalOffset
was registered, it stated that it was read-only:
private static readonly DependencyPropertyKey VerticalOffsetPropertyKey =
DependencyProperty.RegisterReadOnly(nameof (VerticalOffset), typeof (double),
typeof (ScrollViewer), (PropertyMetadata) new FrameworkPropertyMetadata((object) 0.0));
public static readonly DependencyProperty VerticalOffsetProperty =
ScrollViewer.VerticalOffsetPropertyKey.DependencyProperty;
Well, the next step is how to achieve inertial motion in the wheel response method, that is, a declaration motion. People who are familiar with animation quickly know that the WPF animation is ideal for doing inertia, and its description is as follows:
In the figure, the horizontal axis indicates time and the vertical axis indicates the distance of motion. Obviously, the middle EaseOut
pattern is what we want. We can define a property CurrentVerticalOffset
on which we will animate and call ScrollViewer.ScrollToVerticalOffset
in its value callback function to update the scroll position of ScrollViewer
. Of course, we also need a private
field _totalVerticalOffset
, which is used to hold the ScrollViewer
scrolling target position, and the scroll wheel scrolls down one unit and subtracts the value of e.Delta
once, where ‘e
’ is the parameter that the wheel response method passes in. Each time you assign it a value, you can execute the animation on the CurrentVerticalOffset
:BeginAnimation(CurrentVerticalOffsetProperty, animation)
. It is important to note that when a dependent property is changed by animation, the assignment does not take effect because the timeline by default keeps an animation at the end of the activity period until the end of its parent's active and retention period. If you want to manually change the value of dependent properties after the animation, you need to set FillBehavior
to Stop
. But once the animation is finished, the dependent property reverts to the initial value, so subscribe to a Completed
event for the animation, giving the CurrentVerticalOffset
a target value in the event response method, that is, _totalVerticalOffset
.
Finally, there's a conflict that you can't animate when you manually drag a slider or when you use a context menu to change the position of a scroll bar, because OnMouseWheel
is not triggered at this time. That's all right, that's exactly what we want, but it's a problem if we trigger OnMouseWheel
again, because we don't assign values to CurrentVerticalOffset
and _totalVerticalOffset
when manually triggering scrolling(CurrentVerticalOffset
and _totalVerticalOffset
only assign values in OnMouseWheel
), So before you scroll with animations, you need to know if you need to update them first. How do you judge them? We can maintain the state with a private
field _isRunning
. Each time the animation starts, it is assigned a true
, end, then the false
is assigned. So, when it's false
, it means that before calling OnMouseWheel
, the animation is over, the user may have manually changed the scroll bar position (or not, but that doesn't matter), so update the values for the two brothers.
Because most of the usual inertial scrolling is vertical, I do not write horizontal logic, but it is also easy to expand, interested friends can download the source code to modify.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.