Aureo91
Aureo91

Reputation: 71

UWP xaml ripple Effect (android effect) animation

I try to code in UWP app an Android Effect (ripple). So I create an EllipseGeometry Inside a grid (in my usercontrol) but when RadiusX and RadiusY of my ellipseGeometry play their animation, my EllipseGeometry growth out of my grid... I have tried to bounds path with a visible area and clip it to the path, but no success.

Here my XAML code :

<UserControl
x:Class="UIComponents.POIButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UIComponents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="650" Background="White">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gray">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="100"/>
    </Grid.ColumnDefinitions>
    <!--Animation Ellipse-->
    <Grid x:Name="ellipseContainer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="0" Grid.ColumnSpan="3">
        <Path x:Name="path" Fill="Red" Stroke="Black" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
            <Path.Data>
                <EllipseGeometry x:Name="circleGeometry" Center="0,0" RadiusX="5" RadiusY="5" />
            </Path.Data>
        </Path>
    </Grid>
    <Rectangle x:Name="clickableRect" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  PointerPressed="clickableRect_PointerPressed"  Fill="Transparent" Tapped="clickableRect_Tapped"/>
</Grid>
<UserControl.Resources>
    <Storyboard x:Name="RipplePath">
        <DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="RadiusX" Storyboard.TargetName="circleGeometry">
            <EasingDoubleKeyFrame KeyTime="0" Value="5"/>
            <EasingDoubleKeyFrame KeyTime="0:0:10" Value="200"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="RadiusY" Storyboard.TargetName="circleGeometry">
            <EasingDoubleKeyFrame KeyTime="0" Value="5"/>
            <EasingDoubleKeyFrame KeyTime="0:0:10" Value="200"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>

Here my Cs code :

private void clickableRect_Tapped(object sender, TappedRoutedEventArgs e)
    {
        Point touchPosition = e.GetPosition(ellipseContainer);
        //RectangleGeometry visibleArea = new RectangleGeometry();
        //visibleArea.Rect = new Rect(0, 0, 650, 100);
        //path.Clip = visibleArea;
        Storyboard animation = this.FindName("RipplePath") as Storyboard;
        animation.Begin();

    }

Here, the result :

enter image description here

Thanks a lot for your help :)

============== Try Justin XL's solution : result :

Thanks Justin for your Help, It seems the rectanglegeometry is animed too :

enter image description here

But nothing is over the grid.

the Xaml code is :

<UserControl
x:Class="UIComponents.POIButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UIComponents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="650" Background="White">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gray">
    <Grid.Clip>
        <RectangleGeometry Rect="0,0,650,100" />
    </Grid.Clip> ...

and CS code is :

public POIButton()
    {
        this.InitializeComponent();
        var visual = ElementCompositionPreview.GetElementVisual(this);
        visual.Clip = visual.Compositor.CreateInsetClip();
    }

================ XAML Result :

This XAML code not produce correct effect :

<UserControl
x:Class="UIComponents.POIButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UIComponents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="100"
Width="650" Background="White">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gray">
    <Grid.Clip>
        <RectangleGeometry Rect="0,0,650,100" />
    </Grid.Clip>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="100"/>
    </Grid.ColumnDefinitions>
    <!--Animation Ellipse-->
    <Grid x:Name="ellipseContainer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="0" Grid.ColumnSpan="3">
        <Path x:Name="path" Fill="Red" Stroke="Black" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
            <Path.Data>
                <EllipseGeometry x:Name="circleGeometry" Center="0,0" RadiusX="5" RadiusY="5" />
            </Path.Data>
        </Path>
    </Grid>...

Result : enter image description here

But CS Code works fine (with comment the line RectangleGeometry in XAML code) :

this.InitializeComponent();
        var visual = ElementCompositionPreview.GetElementVisual(this);
        visual.Clip = visual.Compositor.CreateInsetClip();

Produce this :

enter image description here

Thanks @Justin XL for it help :)

Upvotes: 3

Views: 1106

Answers (1)

Justin XL
Justin XL

Reputation: 39006

You just need to clip the control from going outside of its boundaries. This can be achieved by either using

<Grid x:Name="Root" HorizontalAlignment="Stretch"
      VerticalAlignment="Stretch"
      Background="Gray">
    <Grid.Clip>
        <RectangleGeometry Rect="0,0,200,80" />
    </Grid.Clip>

in your XAML(200 is the width and 80 is the height), or

public POIButton()
{
    InitializeComponent();

    var visual = ElementCompositionPreview.GetElementVisual(this);
    visual.Clip = visual.Compositor.CreateInsetClip();
}

in code-behind with the new Composition API. Note with the XAML approach you need to manually update the width and height by bindings or code-behind if the size of your control changes, where with Composition, you don't.

Also, I have noticed you used a path animation which runs on the UI thread(e.g. EnableDependentAnimation). This can be replaced by a Ellipse with a ScaleTransform animation which is normally the recommended approach because of its much better performance.


A "ripple" effect with XamlLight

Since you are developing for UWP, it's important to acknowledge what the platform can do, and the UWP way of achieving a similar effect, yet still honoring its own Fluent Design System.

I created the following FluentButton control as shown below with the help of a new XamlLight class introduced in 15063. You will notice that the light follows your mouse cursor, and it ripples upon click/tap.

enter image description here

The second part is done by a customized XamlLight which I called RippleXamlLight, and this is how it's implemented -

First, create a class that inherits from XamlLight.

public class RippleXamlLight : XamlLight

Then, in its OnConnected override method, create a SpotLight instance and a Vector3 animation that will be used to animate the light's Offset. It will also take care of subscribing to pointer events such as PointerPressed.

protected override void OnConnected(UIElement newElement)
{
    _compositor = Window.Current.Compositor;

    var spotLight = CreateSpotLight();
    CompositionLight = spotLight;

    _lightRippleOffsetAnimation = CreateLightRippleOffsetAnimation();

    SubscribeToPointerEvents();

    AddTargetElement(GetId(), newElement);

    ...
    }
}

Finally, kick off the animation whenever the control is pressed. The Offset value is provided by the pointer position and a _rippleOffsetZ which is calculated based on the size of the control.

private void OnPointerPressed(object sender, PointerRoutedEventArgs e) =>
    StartLightRippleOffsetAnimation(e.GetCurrentPoint((UIElement)sender).Position.ToVector2());

private void StartLightRippleOffsetAnimation(Vector2 position)
{
    var startingPoisition = new Vector3(position, 0.0f);
    _lightRippleOffsetAnimation?.InsertKeyFrame(0.0f, startingPoisition);
    _lightRippleOffsetAnimation?.InsertKeyFrame(1.0f, new Vector3(position.X, position.Y, _rippleOffsetZ));

    CompositionLight?.StartAnimation("Offset", _lightRippleOffsetAnimation);
}

In case I didn't explain it clearly enough, here's the full source for your reference. :)

Upvotes: 8

Related Questions