I love Androids new animation where you touch a control (listviewitem, button etc etc) and it does a neat animation like this:
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.
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.
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:
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.