Visually Located

XAML and GIS

Creating a behavior to stretch header content when at the top of a scroller

I’ve been playing around a lot with my wife’s new iPhone a lot lately. One feature I love on some of the apps is when you reach the top of a page a header image will stretch out to indicate you are at the top of the page. This is a fun feature that’s super easy to add using a behavior.

DF7A04AC-B526-4CF7-9F90-FBF4447A113E

The behavior will focus on scaling the image up by a factor but only when the ScrollerViewer is being “stretched”.

public class StretchyHeaderBehavior : Behavior<FrameworkElement>
{
    private ScrollViewer _scroller;
 
    public double StretchyFactor
    {
        get { return (double)GetValue(ScaleFactorProperty); }
        set { SetValue(ScaleFactorProperty, value); }
    }
 
    public static readonly DependencyProperty ScaleFactorProperty = DependencyProperty.Register(
        nameof(StretchyFactor),
        typeof(double),
        typeof(StretchyHeaderBehavior),
        new PropertyMetadata(0.5));
 
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SizeChanged += OnSizeChanged;
        _scroller = AssociatedObject.GetParentOfType<ScrollViewer>();
        if (_scroller == null)
        {
            AssociatedObject.Loaded += OnLoaded;
            return;
        }
        AssignEffect();
    }
 
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        _scroller = AssociatedObject.GetParentOfType<ScrollViewer>();
        AssignEffect();
        AssociatedObject.Loaded -= OnLoaded;
    }
 
    private void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        AssignEffect();
    }
 
    private void AssignEffect()
    {
        if (_scroller == null) return;
 
        CompositionPropertySet scrollerViewerManipulation = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scroller);
 
        var compositor = scrollerViewerManipulation.Compositor;
 
        // See documentation for Lerp and Clamp: 
        // https://msdn.microsoft.com/en-us/windows/uwp/graphics/composition-animation
        var scaleAnimation = compositor.CreateExpressionAnimation(
             "Lerp(1, 1+Amount, Clamp(ScrollManipulation.Translation.Y/50, 0, 1))");
        scaleAnimation.SetScalarParameter("Amount", (float)StretchyFactor);
        scaleAnimation.SetReferenceParameter("ScrollManipulation", scrollerViewerManipulation);
 
        var visual = ElementCompositionPreview.GetElementVisual(AssociatedObject);
        var backgroundImageSize = new Vector2((float)AssociatedObject.ActualWidth, (float)AssociatedObject.ActualHeight);
        visual.Size = backgroundImageSize;
 
        // CenterPoint defaults to the top left (0,0). We want the strecth to occur from the center
        visual.CenterPoint = new Vector3(backgroundImageSize / 2, 1);
        visual.StartAnimation("Scale.X", scaleAnimation);
        visual.StartAnimation("Scale.Y", scaleAnimation);
    }
}

You can find the behavior on my GitHub repo along with a sample project. The sample gif above was even combined with the ParallaxBehavior to give it a little extra fun!

Thanks to Neil Turner for helping come up with the name of the behavior!

blog comments powered by Disqus