Android 在 WPF 中的波纹效果
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29857608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android's Ripple Effect in WPF
提问by Tokfrans
I love Androids new animation where you touch a control (listviewitem, button etc etc) and it does a neat animation like this:
我喜欢 Android 的新动画,您可以在其中触摸控件(列表视图项、按钮等),它会执行如下简洁的动画:


I'm wondering how this can be implemented in a nice way globally for all the 'clickable' controls in WPF.
我想知道如何为 WPF 中的所有“可点击”控件在全局范围内以一种很好的方式实现这一点。
What I specifically need help with is how the circles should be created on the control. The only thing I've thought of was to create own user-controls for each other control (buttons, radiobuttons, etc) where I have a parent for the ellipse as well as the original control itself.
我特别需要帮助的是如何在控件上创建圆圈。我唯一想到的是为每个其他控件(按钮、单选按钮等)创建自己的用户控件,其中我有一个椭圆的父控件以及原始控件本身。
<UserControl>
<Grid MouseLeftButtonDown="handler">
<Button/> <--- this would be the button which you normally would place
</Grid >
</UserControl>
And in the handler-methodthen create an ellipse on the point e.GetPosition(handler)using the margin-properties and later animate it. This solution wouldwork. But it would be a hassle to do this for every control I would want the ripple effect on. Basically something like this:
而在handler-method随后创建点的椭圆e.GetPosition(handler)使用保证金性质,后来制作动画。这个解决方案会起作用。但是对于我想要连锁反应的每个控件都这样做会很麻烦。基本上是这样的:
void handler(object sender, MouseButtonEventArgs e)
{
Grid parent = (Grid)sender;
Ellipse ellipse = new Ellipse();
ellipse.Height = 10; // would be animated
ellipse.Width = 10; // would be animated
Point p = e.GetPosition(parent);
ellipse.Margin = new Thickness(p.X, p.Y, 0, 0);
parent.Children.Add(ellipse);
// do the animation parts to later remove the ellipse
}
Is there a cleaner, more expandable way to place ellipses on my controls other than the way I earlier demonstrated since not all controls support having children?
除了我之前演示的方式之外,是否有一种更简洁、更可扩展的方式在我的控件上放置省略号,因为并非所有控件都支持有孩子?
采纳答案by Domysee
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.
更新:这个问题对我来说很有趣,所以我实现了它。你可以在我的 Github 页面上找到它:https: //github.com/Domysee/WpfCustomControls。有多种自定义控件,您正在寻找的是 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.
我创建了一个继承自 ContentControl、RippleEffectDecorator 的自定义控件。它定义了一个额外的依赖属性 HighlightBackground,用于单击元素后的背景。
The ControlTemplate of RippleEffectDecorator consists of a Grid, an Ellipse and a ContentPresenter.
RippleEffectDecorator 的 ControlTemplate 由 Grid、Ellipse 和 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.
我使用了 Grid 而不是 Border,以便我可以添加多个子元素(Ellipse 和 ContentPresenter 可以重叠)。椭圆将其 Height 属性绑定到自己的宽度,因此它始终是一个圆。
Now to the important part: the animation.
现在到重要的部分:动画。
The Grid defines in its resources a Storyboard, which is played on every MouseDown event.
Grid 在其资源中定义了一个 Storyboard,它在每个 MouseDown 事件上播放。
<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.
椭圆的起始位置、其目标宽度及其在整个效果中的容器位置必须以编程方式设置。我覆盖了 OnApplyTemplate() 方法来向鼠标按下事件添加一个事件处理程序,它启动故事板并设置所有必要的值。
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.
注意:AddHandler() 的最后一个参数决定是否要接收已处理的事件。将此设置为 true 很重要,因为某些 UiElements 处理鼠标事件(例如 Button)。否则 MouseDownEvent 不会触发,因此动画不会执行。
To use it simply add the element on which you want to have this effect as child of RippleEffectDecorator, and the Background to Transparent:
要使用它,只需将要在其上具有此效果的元素添加为 RippleEffectDecorator 的子元素,并将背景添加为透明:
<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.
注2:一些元素包括在鼠标悬停(例如按钮)上设置模板并因此隐藏效果的触发器。如果您不想这样做,则必须设置按钮的模板并删除这些触发器。最简单的方法是使用 Blend,从中获取按钮的模板,删除所有触发器并将其添加为按钮的模板。
回答by user2475096
there also a very cool WPF material design library http://materialdesigninxaml.net/
还有一个非常酷的 WPF 材料设计库 http://materialdesigninxaml.net/
回答by CJK
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.
为此,我建议在 UserControls 上使用自定义控件。几乎所有东西都可以在 xaml 中以这种方式处理。设置控件样式后,您所要做的就是添加一个 Ellipse 并为 ControlTemplate.Triggers 设置一个 MouseDown 或 Button.Pressed 触发器。然后你的动画只需要增加椭圆的高度和宽度,直到波纹效果完成,然后将不透明度淡化为 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.
对于椭圆,请确保位置固定,对于相同的配色方案,尝试使用白色填充和 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.
对于顶部角落按钮上的环形效果,添加第二个椭圆,将填充设置为透明,将描边设置为白色。

