CustomAnimation自定义动画
实现效果:
1、X轴上各种缓动动画效果
2、自定义动画代码实现
文件组织:
一、界面代码
- 界面容器为Grid方格下使用Canvas,Canvas承载TextBlock和Button
- 动画开始的操作有两种方式,一种为点击Go按钮执行所有动画,一种为单独单击按钮会执行相应按钮动画+第一个Linear按钮动画。
- 演示板作为静态资源共享,统一在页面加载触发器,触发条件为上面的2种操作方式。
主要BounceEase、ElasticEase、circle按钮布置代码:
<Canvas Grid.Row="2">
<TextBlock Canvas.Left="85" Canvas.Top="-16" FontSize="108" Foreground="LightGray">Bounce!</TextBlock>
<Button Name="easeInButtonBounce" Canvas.Top="10">
<Button.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Button.RenderTransform>
Beginning!
</Button>
<Button Name="easeOutButtonBounce" Canvas.Top="40">
<Button.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Button.RenderTransform>
End!
</Button>
<Button Name="easeInOutButtonBounce" Canvas.Top="70">
<Button.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Button.RenderTransform>
Both!
</Button>
</Canvas>
主要演示板资源代码:
弹跳BounceDoubleAnimation注册属性有:EdgeBehavior
<Storyboard x:Key="BounceEaseInTimeline">
<customAnimations:BounceDoubleAnimation
From="0" To="500" Duration="0:0:5" EdgeBehavior="EaseIn" Storyboard.TargetName="easeInButtonBounce" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
</Storyboard>
<Storyboard x:Key="BounceEaseOutTimeline">
<customAnimations:BounceDoubleAnimation From="0" To="500" Duration="0:0:5" EdgeBehavior="EaseOut" Storyboard.TargetName="easeOutButtonBounce" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
</Storyboard>
<Storyboard x:Key="BounceEaseInOutTimeline">
<customAnimations:BounceDoubleAnimation From="0" To="500" Duration="0:0:5" EdgeBehavior="EaseInOut" Storyboard.TargetName="easeInOutButtonBounce" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
</Storyboard>
弹簧动画:ElasticDoubleAnimation
<Storyboard x:Key="ElasticEaseInOutTimeline">
<customAnimations:ElasticDoubleAnimation From="0" To="500" Duration="0:0:5" EdgeBehavior="EaseInOut" Storyboard.TargetName="easeInOutButtonElastic" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
</Storyboard>
圆圈BounceEase动画:
ps:其中注释部分是可以省掉而不影响实现效果。
- Circle1动画为沿X轴绕圈运动
- Circle2d动画为在X轴上一点原地绕圈运动,然后平移到终点,注意动画X值点与时间线配合。
<Storyboard x:Key="CircleTimeline">
<DoubleAnimation Duration="0:0:5" From="0" To="500" Storyboard.TargetName="circleButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
<!--<DoubleAnimation Duration="0:0:1.5" From="0" Storyboard.TargetName="circleButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"/>-->
<ParallelTimeline BeginTime="0:0:0" Duration="0:0:4.5">
<customAnimations:CircleAnimation RepeatBehavior="Forever" Duration="0:0:1" Radius="40" Direction="YDirection" Storyboard.TargetName="circleButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"/>
<customAnimations:CircleAnimation RepeatBehavior="Forever" Duration="0:0:1" Radius="40" Direction="XDirection" Storyboard.TargetName="circleButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
</ParallelTimeline>
<!--<ParallelTimeline BeginTime="0:0:4.5">
<DoubleAnimation To="500" Duration="0:0:0.5" Storyboard.TargetName="circleButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
<DoubleAnimation To="0.0" Duration="0:0:0.5" Storyboard.TargetName="circleButton" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"/>
</ParallelTimeline>-->
</Storyboard>
<Storyboard x:Key="CircleTimeline2">
<DoubleAnimation Duration="0:0:1.5" From="0" To="290" Storyboard.TargetName="circleButton2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
<!--<DoubleAnimation Duration="0:0:1.5" From="0" Storyboard.TargetName="circleButton2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"/>-->
<ParallelTimeline BeginTime="0:0:1.5" Duration="0:0:2">
<customAnimations:CircleAnimation RepeatBehavior="Forever" Duration="0:0:0.25" Radius="40" Direction="YDirection" Storyboard.TargetName="circleButton2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"/>
<customAnimations:CircleAnimation RepeatBehavior="Forever" Duration="0:0:0.25" Radius="40" Direction="XDirection" Storyboard.TargetName="circleButton2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
</ParallelTimeline>
<ParallelTimeline BeginTime="0:0:3.5">
<DoubleAnimation To="500" Duration="0:0:1.5" Storyboard.TargetName="circleButton2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"/>
<!--<DoubleAnimation To="0.0" Duration="0:0:0.5" Storyboard.TargetName="circleButton2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"/>-->
</ParallelTimeline>
</Storyboard>
</Window.Resources>
统一触发代码:
<Window.Triggers>
<EventTrigger RoutedEvent="Button.Click" SourceName="goButton">
<BeginStoryboard Storyboard="{StaticResource LinearTimeline}"/>
<BeginStoryboard Storyboard="{StaticResource AccelTimeline}"/>
<BeginStoryboard Storyboard="{StaticResource DecelTimeline}"/>
<BeginStoryboard Storyboard="{StaticResource AccelDecelTimeline}"/>
<BeginStoryboard Storyboard="{StaticResource BounceEaseInTimeline}"/>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="linearButton">
<BeginStoryboard Storyboard="{StaticResource LinearTimeline}"/>
</EventTrigger>
后台代码:
本实例自定义实现缓动函数EasingFunctionBase的一部分具体子效果,在xaml示例中还可以设置各项缓动效果的属性参数,详细见第2章。
BounceAnimation弹球动画自定义:
- Bounce自定义弹球算法:此处不详述,只知道有个大概方法,获取特定公式函数值,然后根据EaseMode来增减量返回
- .Net中BounceEase与此原理相识,都可以设置Bounces、Bounciness参数
protected override double GetCurrentValueCore(
double defaultOriginValue,
double defaultDestinationValue,
AnimationClock clock)
{
double returnValue;
var start = From ?? defaultOriginValue;
var delta = To - start ?? defaultOriginValue - start;
switch (EdgeBehavior)
{
case EdgeBehaviorEnum.EaseIn:
returnValue = EaseIn(clock.CurrentProgress.Value, start, delta, Bounciness, Bounces);
break;
case EdgeBehaviorEnum.EaseOut:
returnValue = EaseOut(clock.CurrentProgress.Value, start, delta, Bounciness, Bounces);
break;
default:
returnValue = EaseInOut(clock.CurrentProgress.Value, start, delta, Bounciness, Bounces);
break;
}
return returnValue;
}
protected override Freezable CreateInstanceCore() => new BounceDoubleAnimation();
private static double EaseOut(double timeFraction, double start, double delta, double bounciness, int bounces)
{
// math magic: The cosine gives us the right wave, the timeFraction is the frequency of the wave,
// the absolute value keeps every value positive (so it "bounces" off the midpoint of the cosine
// wave, and the amplitude (the exponent) makes the sine wave get smaller and smaller at the end.
var returnValue = Math.Abs(Math.Pow((1 - timeFraction), bounciness)
*Math.Cos(2*Math.PI*timeFraction*bounces));
returnValue = delta - (returnValue*delta);
returnValue += start;
return returnValue;
}
private static double EaseIn(double timeFraction, double start, double delta, double bounciness, int bounces)
{
// math magic: The cosine gives us the right wave, the timeFraction is the amplitude of the wave,
// the absolute value keeps every value positive (so it "bounces" off the midpoint of the cosine
// wave, and the amplitude (the exponent) makes the sine wave get bigger and bigger towards the end.
var returnValue = Math.Abs(Math.Pow((timeFraction), bounciness)
*Math.Cos(2*Math.PI*timeFraction*bounces));
returnValue = returnValue*delta;
returnValue += start;
return returnValue;
}
private static double EaseInOut(double timeFraction, double start, double delta, double bounciness, int bounces)
{
double returnValue;
// we cut each effect in half by multiplying the time fraction by two and halving the distance.
if (timeFraction <= 0.5)
{
returnValue = EaseIn(timeFraction*2, start, delta/2, bounciness, bounces);
}
else
{
returnValue = EaseOut((timeFraction - 0.5)*2, start, delta/2, bounciness, bounces);
returnValue += delta/2;
}
return returnValue;
}
ElasticDoubleAnimation弹簧动画自定义
- 弹簧动画,结构方式与上面类似,其属性参与类似ElasticEaseh缓动函数
- 算法如下:
protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue,
AnimationClock clock)
{
double returnValue;
var start = From ?? defaultOriginValue;
var delta = To - start ?? defaultOriginValue - start;
switch (EdgeBehavior)
{
case EdgeBehaviorEnum.EaseIn:
returnValue = EaseIn(clock.CurrentProgress.Value, start, delta, Springiness, Oscillations);
break;
case EdgeBehaviorEnum.EaseOut:
returnValue = EaseOut(clock.CurrentProgress.Value, start, delta, Springiness, Oscillations);
break;
default:
returnValue = EaseInOut(clock.CurrentProgress.Value, start, delta, Springiness, Oscillations);
break;
}
return returnValue;
}
protected override Freezable CreateInstanceCore() => new ElasticDoubleAnimation();
private static double EaseOut(double timeFraction, double start, double delta, double springiness,
double oscillations)
{
// math magic: The cosine gives us the right wave, the timeFraction * the # of oscillations is the
// frequency of the wave, and the amplitude (the exponent) makes the wave get smaller at the end
// by the "springiness" factor. This is extremely similar to the bounce equation.
var returnValue = Math.Pow((1 - timeFraction), springiness)
*Math.Cos(2*Math.PI*timeFraction*oscillations);
returnValue = delta - (returnValue*delta);
returnValue += start;
return returnValue;
}
private static double EaseIn(double timeFraction, double start, double delta, double springiness,
double oscillations)
{
// math magic: The cosine gives us the right wave, the timeFraction * the # of oscillations is the
// frequency of the wave, and the amplitude (the exponent) makes the wave get smaller at the beginning
// by the "springiness" factor. This is extremely similar to the bounce equation.
var returnValue = Math.Pow((timeFraction), springiness)
*Math.Cos(2*Math.PI*timeFraction*oscillations);
returnValue = returnValue*delta;
returnValue += start;
return returnValue;
}
private static double EaseInOut(double timeFraction, double start, double delta, double springiness,
double oscillations)
{
double returnValue;
// we cut each effect in half by multiplying the time fraction by two and halving the distance.
if (timeFraction <= 0.5)
{
return EaseIn(timeFraction*2, start, delta/2, springiness, oscillations);
}
returnValue = EaseOut((timeFraction - 0.5)*2, start, delta/2, springiness, oscillations);
returnValue += (delta/2);
return returnValue;
}
}
CircleAnimation圆圈动画自定义:
- 与.NET的CircleEase完全不同,后者无设置参数,而前者有X、Y方向、半径大小属性参数。
- 另外是原地圆圈运动,所有不像上述自定义属性From/To等
- 同时确定X、Y轴方向动画形成了原地圆圈动画,且看其简约的代码:
/// <summary>
/// CircleAnimation: calculates polar coordinates as a function of time.
/// Use two of these (XDirection and YDirection) to move an element in an elliptical manner
/// </summary>
public class CircleAnimation : DoubleAnimationBase
{
public enum DirectionEnum
{
XDirection,
YDirection
}
public static readonly DependencyProperty DirectionProperty =
DependencyProperty.Register("Direction", typeof (DirectionEnum), typeof (CircleAnimation),
new PropertyMetadata(DirectionEnum.XDirection));
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof (double), typeof (CircleAnimation),
new PropertyMetadata((double) 10));
/// <summary>
/// distance from origin to polar coordinate
/// </summary>
public double Radius
{
get { return (double) GetValue(RadiusProperty); }
set
{
if (value > 0.0)
{
SetValue(RadiusProperty, value);
}
else
{
throw new ArgumentException("a radius of " + value + " is not allowed!");
}
}
}
/// <summary>
/// are we measuring in the X or Y direction?
/// </summary>
public DirectionEnum Direction
{
get { return (DirectionEnum) GetValue(DirectionProperty); }
set { SetValue(DirectionProperty, value); }
}
protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue,
AnimationClock clock)
{
var time = clock.CurrentProgress.Value;
// math magic: calculate new coordinates using polar coordinate equations. This requires two
// animations to be wired up in order to move in a circle, since we don't make any assumptions
// about what we're animating (e.g. a TranslateTransform).
var returnValue = Direction == DirectionEnum.XDirection
? Math.Cos(2*Math.PI*time)
: Math.Sin(2*Math.PI*time);
// Need to add the defaultOriginValue so that composition works.
return returnValue*Radius + defaultOriginValue;
}
protected override Freezable CreateInstanceCore() => new CircleAnimation();
}
BackDoubleAnimation后退下动画效果
- 同理BackDoubleAnimation自定义动画与BackEase参数不一样,前者只有Amplitude/Suppression,h后者只有Amplitudesh属性。
- 完整代码如下:
/// <summary>
/// BackDoubleAnimation: goes in the opposite direction first
/// </summary>
public class BackDoubleAnimation : DoubleAnimationBase
{
public enum EdgeBehaviorEnum
{
EaseIn,
EaseOut,
EaseInOut
}
public static readonly DependencyProperty EdgeBehaviorProperty =
DependencyProperty.Register("EdgeBehavior", typeof (EdgeBehaviorEnum), typeof (BackDoubleAnimation),
new PropertyMetadata(EdgeBehaviorEnum.EaseIn));
public static readonly DependencyProperty AmplitudeProperty =
DependencyProperty.Register("Amplitude", typeof (double), typeof (BackDoubleAnimation),
new PropertyMetadata(4.0));
public static readonly DependencyProperty SuppressionProperty =
DependencyProperty.Register("Suppression", typeof (double), typeof (BackDoubleAnimation),
new PropertyMetadata(2.0));
public static readonly DependencyProperty FromProperty =
DependencyProperty.Register("From",
typeof (double?),
typeof (BackDoubleAnimation),
new PropertyMetadata(null));
public static readonly DependencyProperty ToProperty =
DependencyProperty.Register("To",
typeof (double?),
typeof (BackDoubleAnimation),
new PropertyMetadata(null));
/// <summary>
/// which side gets the effect
/// </summary>
public EdgeBehaviorEnum EdgeBehavior
{
get { return (EdgeBehaviorEnum) GetValue(EdgeBehaviorProperty); }
set { SetValue(EdgeBehaviorProperty, value); }
}
/// <summary>
/// how much backwards motion is there in the effect
/// </summary>
public double Amplitude
{
get { return (double) GetValue(AmplitudeProperty); }
set { SetValue(AmplitudeProperty, value); }
}
/// <summary>
/// how quickly the effect drops off vs. the entire timeline
/// </summary>
public double Suppression
{
get { return (double) GetValue(SuppressionProperty); }
set { SetValue(SuppressionProperty, value); }
}
/// <summary>
/// Specifies the starting value of the animation.
/// </summary>
public double? From
{
get { return (double?) GetValue(FromProperty); }
set { SetValue(FromProperty, value); }
}
/// <summary>
/// Specifies the ending value of the animation.
/// </summary>
public double? To
{
get { return (double?) GetValue(ToProperty); }
set { SetValue(ToProperty, value); }
}
protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue,
AnimationClock clock)
{
double returnValue;
var start = From ?? defaultOriginValue;
var delta = To - start ?? defaultOriginValue - start;
switch (EdgeBehavior)
{
case EdgeBehaviorEnum.EaseIn:
returnValue = EaseIn(clock.CurrentProgress.Value, start, delta, Amplitude, Suppression);
break;
case EdgeBehaviorEnum.EaseOut:
returnValue = EaseOut(clock.CurrentProgress.Value, start, delta, Amplitude, Suppression);
break;
default:
returnValue = EaseInOut(clock.CurrentProgress.Value, start, delta, Amplitude, Suppression);
break;
}
return returnValue;
}
protected override Freezable CreateInstanceCore() => new BackDoubleAnimation();
private static double EaseOut(double timeFraction, double start, double delta, double amplitude,
double suppression)
{
var frequency = 0.5;
// math magic: The sine gives us the right wave, the timeFraction * 0.5 (frequency) gives us only half
// of the full wave, the amplitude gives us the relative height of the peak, and the exponent makes the
// effect drop off more quickly by the "suppression" factor.
var returnValue = Math.Pow((timeFraction), suppression)
*amplitude*Math.Sin(2*Math.PI*timeFraction*frequency) + timeFraction;
returnValue = (returnValue*delta);
returnValue += start;
return returnValue;
}
private static double EaseIn(double timeFraction, double start, double delta, double amplitude,
double suppression)
{
var frequency = 0.5;
// math magic: The sine gives us the right wave, the timeFraction * 0.5 (frequency) gives us only half
// of the full wave (flipped by multiplying by -1 so that we go "backwards" first), the amplitude gives
// us the relative height of the peak, and the exponent makes the effect start later by the "suppression"
// factor.
var returnValue = Math.Pow((1 - timeFraction), suppression)
*amplitude*Math.Sin(2*Math.PI*timeFraction*frequency)*-1 + timeFraction;
returnValue = (returnValue*delta);
returnValue += start;
return returnValue;
}
private static double EaseInOut(double timeFraction, double start, double delta, double amplitude,
double suppression)
{
double returnValue;
// we cut each effect in half by multiplying the time fraction by two and halving the distance.
if (timeFraction <= 0.5)
{
return EaseIn(timeFraction*2, start, delta/2, amplitude, suppression);
}
returnValue = EaseOut((timeFraction - 0.5)*2, start, delta/2, amplitude, suppression);
returnValue += (delta/2);
return returnValue;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。