Android在WPF中的涟漪效应

Tok*_*ans 26 c# wpf xaml

我喜欢Androids新动画,你触摸一个控件(listviewitem,按钮等等),它做了一个像这样的整洁动画:

在此输入图像描述

我想知道如何在全局范围内为WPF中的所有"可点击"控件实现这一点.


我特别需要帮助的是如何在控件上创建圆圈.我唯一想到的是为每个其他控件(按钮,单选按钮等)创建自己的用户控件,其中我有椭圆的父级以及原始控件本身.

<UserControl>
   <Grid MouseLeftButtonDown="handler">
      <Button/> <--- this would be the button which you normally would place
   </Grid >
</UserControl>
Run Code Online (Sandbox Code Playgroud)

而在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
}
Run Code Online (Sandbox Code Playgroud)

除了我之前演示的方式之外,是否有更清晰,更可扩展的方法将椭圆放在我的控件上,因为并非所有控件都支持生孩子?

Dom*_*see 17

更新: 这个问题对我来说非常有趣,我实现了它.您可以在我的Github页面上找到它:https://github.com/Domysee/WpfCustomControls.有多个自定义控件,您正在寻找的是RippleEffectDecorator.


现在我解释一下我做了什么:

我创建了一个继承自ContentControl,RippleEffectDecorator的自定义控件.它定义了一个额外的依赖项属性HighlightBackground,它在您单击元素后用于背景.

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>
Run Code Online (Sandbox Code Playgroud)

我使用了Grid而不是Border,这样我就可以添加多个子元素(Ellipse和ContentPresenter可以重叠).椭圆将其Height属性绑定到自己的宽度,因此它始终是一个圆.

现在重要的部分:动画.

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>
Run Code Online (Sandbox Code Playgroud)

故事板为椭圆的宽度属性设置动画,使其完全填充该区域.它还必须为边距设置动画,因为椭圆相对于左上角(不是围绕其中心)定位自身.

必须以编程方式设置椭圆的起始位置,其目标宽度及其在整个效果中在容器中的位置.我覆盖了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);
}
Run Code Online (Sandbox Code Playgroud)

注意: AddHandler()的最后一个参数确定是否要接收已处理的事件.将此设置为true非常重要,因为某些UiElements处理鼠标事件(例如Button).否则,MouseDownEvent不会触发,因此动画不会执行.

要使用它,只需添加您想要具有此效果的元素作为RippleEffectDecorator的子元素,并将背景添加到透明:

<cc:RippleEffectDecorator Background="Green" HighlightBackground="LightGreen">
    <Button FontSize="60" Background="Transparent">stuff</Button>
</cc:RippleEffectDecorator>
Run Code Online (Sandbox Code Playgroud)

注意2:一些元素包括在MouseOver上设置模板的触发器(例如Button),因此隐藏了效果.如果您不想要,则必须设置按钮的模板并删除这些触发器.最简单的方法是使用Blend,从中获取按钮的模板,删除所有触发器并将其添加为按钮的模板.

  • 这很好,虽然我会使用不同的方法.我记得在WPFShaderEffectLibrary中看到了一个RippleEffect.无论如何,如果你把它放到Nuget中让任何人享受和重复使用它会很好;) (2认同)

use*_*096 5

还有一个非常酷的 WPF 材料设计库 http://materialdesigninxaml.net/