Android's Ripple Effect in WPF

前端 未结 3 1968
萌比男神i
萌比男神i 2021-01-31 18:05

I love Androids new animation where you touch a control (listviewitem, button etc etc) and it does a neat animation like this:

相关标签:
3条回答
  • 2021-01-31 18:26

    UPDATE: This problem was so interesting to me that I implemented it. You can find it on my Github page: https://github.com/Domysee/WpfCustomControls. There are multiple custom controls, the one you are looking for is RippleEffectDecorator.


    Now I explain what I did:

    I created a custom control that inherits from ContentControl, RippleEffectDecorator. It defines an additional dependency property HighlightBackground, which is used for the background after you clicked the element.

    The ControlTemplate of RippleEffectDecorator consists of a Grid, an Ellipse and a ContentPresenter.

    <ControlTemplate TargetType="{x:Type l:RippleEffectDecorator}">
        <Grid x:Name="PART_grid" ClipToBounds="True" Background="{TemplateBinding Background}"
                Width="{Binding ElementName=PART_contentpresenter, Path=ActualWidth}"
                Height="{Binding ElementName=PART_contentpresenter, Path=ActualHeight}">
            <Ellipse x:Name="PART_ellipse"
                            Fill="{Binding Path=HighlightBackground, RelativeSource={RelativeSource TemplatedParent}}" 
                            Width="0" Height="{Binding Path=Width, RelativeSource={RelativeSource Self}}" 
                            HorizontalAlignment="Left" VerticalAlignment="Top"/>
    
            <ContentPresenter x:Name="PART_contentpresenter" />
        </Grid>
    </ControlTemplate>
    

    I used a Grid instead of a Border so that I can add multiple child elements (necessary that Ellipse and ContentPresenter can overlap). The ellipse binds its Height property to its own width, so that it is always a circle.

    Now to the important part: the animation.

    The Grid defines in its resources a Storyboard, which is played on every MouseDown event.

    <Storyboard x:Key="PART_animation" Storyboard.TargetName="PART_ellipse">
        <DoubleAnimation Storyboard.TargetProperty="Width" From="0" />
        <ThicknessAnimation Storyboard.TargetProperty="Margin" />
        <DoubleAnimation BeginTime="0:0:1" Duration="0:0:0.25" Storyboard.TargetProperty="Opacity"
                    From="1" To="0" />
        <DoubleAnimation Storyboard.TargetProperty="Width" To="0" BeginTime="0:0:1.25" Duration="0:0:0" />
        <DoubleAnimation BeginTime="0:0:1.25" Duration="0:0:0" Storyboard.TargetProperty="Opacity" To="1" />
    </Storyboard>
    

    The storyboard animates the width property of the ellipse so that it fills the area completely. It also has to animate the Margin, because the ellipse positions itself relative to the upper left point (not around its center).

    The start position of the ellipse, its target width and its position in the container throughout the effect has to be set programmatically. I overwrite the OnApplyTemplate() method to add an event handler to the mouse down event, which starts the storyboard and sets all necessary values.

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
    
        ellipse = GetTemplateChild("PART_ellipse") as Ellipse;
        grid = GetTemplateChild("PART_grid") as Grid;
        animation = grid.FindResource("PART_animation") as Storyboard;
    
        this.AddHandler(MouseDownEvent, new RoutedEventHandler((sender, e) =>
        {
            var targetWidth = Math.Max(ActualWidth, ActualHeight) * 2;
            var mousePosition = (e as MouseButtonEventArgs).GetPosition(this);
            var startMargin = new Thickness(mousePosition.X, mousePosition.Y, 0, 0);
            //set initial margin to mouse position
            ellipse.Margin = startMargin;
            //set the to value of the animation that animates the width to the target width
            (animation.Children[0] as DoubleAnimation).To = targetWidth;
            //set the to and from values of the animation that animates the distance relative to the container (grid)
            (animation.Children[1] as ThicknessAnimation).From = startMargin;
            (animation.Children[1] as ThicknessAnimation).To = new Thickness(mousePosition.X - targetWidth / 2, mousePosition.Y - targetWidth / 2, 0, 0);
            ellipse.BeginStoryboard(animation);
        }), true);
    }
    

    Note: the last parameter of AddHandler() determines whether or not you want to receive handled events. It is important to set this to true, because some UiElements handle mouse events (e.g. Button). Otherwise the MouseDownEvent would not fire and therefore the animation not executed.

    To use it simply add the element on which you want to have this effect as child of RippleEffectDecorator, and the Background to Transparent:

    <cc:RippleEffectDecorator Background="Green" HighlightBackground="LightGreen">
        <Button FontSize="60" Background="Transparent">stuff</Button>
    </cc:RippleEffectDecorator>
    

    Note2: some elements include triggers which set the template on MouseOver (e.g. Button) and therefore hide the effect. If you dont want that you have to set the template of the button and remove these triggers. The easiest way is to use Blend, get the template of the button from it, remove all triggers and add it as template of your button.

    0 讨论(0)
  • 2021-01-31 18:40

    I suggest using Custom Controls over UserControls for this. Almost everything could be handled in xaml that way. Once you have your control styled then all you have to do is add an Ellipse and set a MouseDown or Button.Pressed trigger to your ControlTemplate.Triggers. Then your animation would only need to increase the height and width of the Ellipse until the ripple effect was completed then fade the Opacity to 0.

    For the Ellipse make sure you have the position fixed and for the same color scheme try a white fill and an opacity of 0.3-5.

    For the ring effect on the button in the top corner of your add a 2nd Ellipse set the Fill to Transparent and the Stroke to White.

    0 讨论(0)
  • 2021-01-31 18:42

    there also a very cool WPF material design library http://materialdesigninxaml.net/

    0 讨论(0)
提交回复
热议问题