ParticleEffets例子效果
实现效果:
- 界面上的按钮为两种,一是点击会爆发出微粒,二是点击会出现环形圆圈警告模样。
- 按钮之间的碰撞及与边界、移动距离及速度的关系受滚动条参数的控制,
代码组织:
文件解释:
- ParticleEffectExamples.xaml实现以上界面的文件,包括调参数Slider控件、包含按钮的自定义FireworkEffect、SonicEffect控件及容器MagnitismCanvas控件
- OverlayRenderDecoratorOverlayVisual.cs继承DrawingVisual类,实现管理是否需要当前Visual类的呈现执行命中测试
- OverlayRenderDecorator.cs中 重载呈现装饰类OverlayRenderDecorator继承框架类FrameworkElement,聚集上面的OverlayRenderDecoratorOverlayVisual字段、可视化集合类VisualCollection、作为子内容UIElement类的引用。实现添加逻辑内容按钮并点击时会呈现由子类实现的具体呈现内容,如爆发微粒,警告模样呈现。
- FireworkEffect.cs文件继承以上装饰类OverlayRenderDecorator实现爆发微粒绘图的每帧呈现,由鼠标点击触发及爆发微粒的每帧可视化呈现 来实现。其中包含微粒的生成、数量、随机出现下落位置、消失及更新。
- SonicEffect.cs文件同上,触发圆环的绘制,包含圆环的数量、大小等。
- MagnitismCanvas.cs文件继承画布Canvas,设置三个参数:摇摆幅度Drag、边界作用力BorderForce、子项间的相互作用力ChildForce
- ParticleEffectsTimeTracker.cs文件提供基本的时间增量驱动,对MagnitismCanvas来说,获取每帧的时间间隔与内容项的位移参数;对FireworkEffect来说也提供帧间时间值,判断是否大于存在时间;对SonicEffet来说暂时未确定,因为更改附件属性RingDelay未起作用。
界面xaml:
<Canvas x:Name="PageCanvas">
<ae:MagnitismCanvas x:Name="MagCanvas" Drag="0.8" BorderForce="1000" ChildForce="100"
DataContext="{Binding ElementName=PageCanvas}" Width="{Binding Path=ActualWidth}" Height="{Binding Path=ActualHeight}">
<ae:FireworkEffect Canvas.Top="100" Canvas.Left="100">
<ae:FireworkEffect.Child>
<Button>Click For Fireworks</Button>
</ae:FireworkEffect.Child>
</ae:FireworkEffect>
<ae:SonicEffect Canvas.Top="200" Canvas.Left="200" RingThickness="10" RingRadius="15" RingDelay="0:0:2" RingColor="#22553366">
<ae:SonicEffect.Child>
<Button>Click For Sonic burst</Button>
</ae:SonicEffect.Child>
</ae:SonicEffect>
OverlayRenderDecoratorOverlayVisual:
/// <summary>
/// This class is used internally to delegate it's rendering to the parent OverlayRenderDecorator.
/// </summary>
internal class OverlayRenderDecoratorOverlayVisual : DrawingVisual
{
internal bool IsHitTestVisible { get; set; } = false;
//dont hit test, these are just overlay graphics
protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
{
if (IsHitTestVisible)
return base.HitTestCore(hitTestParameters);
return null;
}
//dont hit test, these are just overlay graphics
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
if (IsHitTestVisible)
return base.HitTestCore(hitTestParameters);
return null;
}
}
/// <summary>
/// OverlayRenderDecorator provides a simple overlay graphics mechanism similar
/// to OnRender called OnOverlayRender
/// </summary>
[ContentProperty("Child")]
public class OverlayRenderDecorator : FrameworkElement
{
//this will be a visual child, but not a logical child. as the last child, it can 'overlay' graphics
// by calling back to us with OnOverlayRender
private readonly OverlayRenderDecoratorOverlayVisual _overlayVisual;
private readonly VisualCollection _vc;
//the only logical child
private UIElement _child;
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public OverlayRenderDecorator()
{
_overlayVisual = new OverlayRenderDecoratorOverlayVisual();
_vc = new VisualCollection(this) {_overlayVisual};
//insert the overlay element into the visual tree
}
#endregion
//-------------------------------------------------------------------
//
// Public Properties
//
//-------------------------------------------------------------------
#region Public Properties
/// <summary>
/// Enables/Disables hit testing on the overlay visual
/// </summary>
public bool IsOverlayHitTestVisible
{
get
{
if (_overlayVisual != null)
return _overlayVisual.IsHitTestVisible;
return false;
}
set
{
if (_overlayVisual != null)
_overlayVisual.IsHitTestVisible = value;
}
}
/// <summary>
/// The single child of an <see cref="System.Windows.Media.Animation.OverlayRenderDecorator" />
/// </summary>
[DefaultValue(null)]
public virtual UIElement Child
{
get { return _child; }
set
{
if (_child != value)
{
//need to remove old element from logical tree
if (_child != null)
{
OnDetachChild(_child);
RemoveLogicalChild(_child);
}
_vc.Clear();
if (value != null)
{
//add to visual
_vc.Add(value);
//add to logical
AddLogicalChild(value);
}
//always add the overlay child back into the visual tree if its set
if (_overlayVisual != null)
_vc.Add(_overlayVisual);
//cache the new child
_child = value;
//notify derived types of the new child
if (value != null)
OnAttachChild(_child);
InvalidateMeasure();
}
}
}
/// <summary>
/// Returns enumerator to logical children.
/// </summary>
protected override IEnumerator LogicalChildren
{
get
{
if (_child == null)
{
return new List<UIElement>().GetEnumerator();
}
var l = new List<UIElement> {_child};
return l.GetEnumerator();
}
}
#endregion
//-------------------------------------------------------------------
//
// Protected Methods
//
//-------------------------------------------------------------------
#region Protected Methods
/// <summary>
/// Updates DesiredSize of the OverlayRenderDecorator. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <remarks>
/// OverlayRenderDecorator determines a desired size it needs from the child's sizing properties, margin, and requested
/// size.
/// </remarks>
/// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
/// <returns>The OverlayRenderDecorator's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
var child = Child;
if (child != null)
{
child.Measure(constraint);
return (child.DesiredSize);
}
return (new Size());
}
/// <summary>
/// OverlayRenderDecorator computes the position of its single child inside child's Margin and calls Arrange
/// on the child.
/// </summary>
/// <param name="arrangeSize">Size the OverlayRenderDecorator will assume.</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
var child = Child;
child?.Arrange(new Rect(arrangeSize));
//Our OnRender gets called in Arrange, but
// we dont have access to that, so update the
// overlay here.
if (_overlayVisual != null)
{
using (var dc = _overlayVisual.RenderOpen())
{
//delegate to derived types
OnOverlayRender(dc);
}
}
return (arrangeSize);
}
/// <summary>
/// render method for overlay graphics.
/// </summary>
/// <param name="dc"></param>
protected virtual void OnOverlayRender(DrawingContext dc)
{
}
/// <summary>
/// gives derives types a simple way to respond to a new child being added
/// </summary>
/// <param name="child"></param>
protected virtual void OnAttachChild(UIElement child)
{
}
/// <summary>
/// gives derives types a simple way to respond to a child being removed
/// </summary>
/// <param name="child"></param>
protected virtual void OnDetachChild(UIElement child)
{
}
protected override int VisualChildrenCount => _vc.Count;
protected override Visual GetVisualChild(int index) => _vc[index];
#endregion
}
FireworkEffect:
//uielement
//visual operations
//animation effect stuff
//LayoutOverrideDecorator
public class FireworkEffect : OverlayRenderDecorator
{
private readonly Vector _gravity = new Vector(0.0, 10.0);
private readonly List<Particle> _particles = new List<Particle>();
private Point _lastMousePosition = new Point(0, 0);
private double _secondsUntilDrop;
private ParticleEffectsTimeTracker _timeTracker;
protected Random Random = new Random();
protected override void OnAttachChild(UIElement child)
{
CompositionTarget.Rendering += OnFrameCallback;
child.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
child.PreviewMouseMove += OnMouseMove;
_timeTracker = new ParticleEffectsTimeTracker();
}
protected override void OnDetachChild(UIElement child)
{
CompositionTarget.Rendering -= OnFrameCallback;
child.PreviewMouseLeftButtonUp -= OnMouseLeftButtonUp;
child.PreviewMouseMove -= OnMouseMove;
_timeTracker = null;
}
protected void OnFrameCallback(object sender, EventArgs e)
{
UpdateParticles(_timeTracker.Update());
}
protected virtual void UpdateParticles(double deltatime)
{
//drop particles from mouse position
if (MouseDropsParticles && IsMouseOver)
{
_secondsUntilDrop -= deltatime;
if (_secondsUntilDrop < 0.0)
{
AddRandomBurst(_lastMousePosition - new Vector(Radius/2.0, Radius/2.0), 1);
_secondsUntilDrop = MouseDropDelay + (Random.NextDouble()*2.0 - 1.0)*MouseDropDelayVariation;
}
}
if (_particles.Count > 0)
InvalidateVisual();
//update all particles
for (var i = 0; i < _particles.Count;)
{
//_particles[i]
var p = _particles[i];
if (GravitateToMouse)
{
p.Velocity += (_lastMousePosition - p.Location)*GravitateScale;
}
else
{
p.Velocity += _gravity;
}
p.Location += p.Velocity*deltatime;
if (BounceOffContainer)
{
var radius = p.Diameter/2.0;
if (p.Location.X - radius < 0.0)
{
p.Location.X = radius;
p.Velocity.X *= -0.9;
}
else if (p.Location.X > ActualWidth - radius)
{
p.Location.X = ActualWidth - radius;
p.Velocity.X *= -0.9;
}
if (p.Location.Y - radius < 0.0)
{
p.Location.Y = radius;
p.Velocity.Y *= -0.9;
}
else if (p.Location.Y > ActualHeight - radius)
{
p.Location.Y = ActualHeight - radius;
p.Velocity.Y *= -0.9;
}
}
//only increment counter for live particles
if (_timeTracker.ElapsedTime > p.DeathTime)
_particles.RemoveAt(i);
else
i++;
}
}
protected override void OnOverlayRender(DrawingContext drawingContext)
{
foreach (var p in _particles)
{
//figure out where in the particles life we are
var particlelife = (_timeTracker.ElapsedTime - p.LifeTime).TotalSeconds/
(p.DeathTime - p.LifeTime).TotalSeconds;
var currentcolor = Color.Multiply(p.StartColor, (float) (1.0 - particlelife)) +
Color.Multiply(p.EndColor, (float) particlelife);
Brush brush = new RadialGradientBrush(currentcolor,
Color.FromArgb(0, currentcolor.R, currentcolor.G, currentcolor.B));
var rect =
new RectangleGeometry(
new Rect(new Point(p.Location.X - p.Diameter/2.0, p.Location.Y - p.Diameter/2.0),
new Size(p.Diameter, p.Diameter)));
drawingContext.DrawGeometry(brush, null, rect);
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
_lastMousePosition = e.GetPosition(this);
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
AddRandomBurst(e.GetPosition(this));
}
public void AddRandomBurst()
{
var point = new Point(Random.NextDouble()*ActualWidth, Random.NextDouble()*ActualHeight);
AddRandomBurst(point, ClickBurstSize);
}
public void AddRandomBurst(Point location)
{
AddRandomBurst(location, ClickBurstSize);
}
public void AddRandomBurst(Point location, int count)
{
for (var i = 0; i < count; i++)
{
var p = new Particle();
var radius = Radius + (Random.NextDouble()*2.0 - 1.0)*RadiusVariation;
var lifetime = Random.NextDouble()*3.0 + 1.0;
p.Location = location;
p.Velocity = new Vector(Random.NextDouble()*200.0 - 100.0, Random.NextDouble()*-200 + 100.0);
p.LifeTime = _timeTracker.ElapsedTime;
p.DeathTime = _timeTracker.ElapsedTime + TimeSpan.FromSeconds(lifetime);
p.Diameter = 2.0*radius;
var startColor = StartColor;
var startColorVariation = StartColorVariation;
var endColor = EndColor;
var endColorVariation = EndColorVariation;
var startRandColor = Color.FromScRgb(startColorVariation.ScA*(float) (Random.NextDouble()*2.0 - 1.0),
startColorVariation.ScR*(float) (Random.NextDouble()*2.0 - 1.0),
startColorVariation.ScG*(float) (Random.NextDouble()*2.0 - 1.0),
startColorVariation.ScB*(float) (Random.NextDouble()*2.0 - 1.0));
var endRandColor = Color.FromScRgb(endColorVariation.ScA*(float) (Random.NextDouble()*2.0 - 1.0),
endColorVariation.ScR*(float) (Random.NextDouble()*2.0 - 1.0),
endColorVariation.ScG*(float) (Random.NextDouble()*2.0 - 1.0),
endColorVariation.ScB*(float) (Random.NextDouble()*2.0 - 1.0));
p.StartColor = startColor + startRandColor;
p.EndColor = endColor + endRandColor;
_particles.Add(p);
}
}
#region Dependency Properties
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register(
"RadiusBase",
typeof (double),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(15.0)
);
public static readonly DependencyProperty RadiusVariationProperty =
DependencyProperty.Register(
"RadiusVariation",
typeof (double),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(5.0)
);
public static readonly DependencyProperty StartColorProperty =
DependencyProperty.Register(
"StartColor",
typeof (Color),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(Color.FromArgb(245, 200, 50, 20))
);
public static readonly DependencyProperty EndColorProperty =
DependencyProperty.Register(
"EndColor",
typeof (Color),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(Color.FromArgb(100, 200, 255, 20))
);
public static readonly DependencyProperty StartColorVariationProperty =
DependencyProperty.Register(
"StartColorVariation",
typeof (Color),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(Color.FromArgb(10, 55, 50, 20))
);
public static readonly DependencyProperty EndColorVariationProperty =
DependencyProperty.Register(
"EndColorVariation",
typeof (Color),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(Color.FromArgb(50, 20, 100, 20))
);
public static readonly DependencyProperty MouseDropDelayProperty =
DependencyProperty.Register(
"MouseDropDelay",
typeof (double),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(0.1)
);
public static readonly DependencyProperty MouseDropDelayVariationProperty =
DependencyProperty.Register(
"MouseDropDelayVariation",
typeof (double),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(0.05)
);
public static readonly DependencyProperty ClickBurstSizeProperty =
DependencyProperty.Register(
"ClickBurstSize",
typeof (int),
typeof (FireworkEffect),
new FrameworkPropertyMetadata(30)
);
#endregion
#region Properties
public double Radius
{
get { return (double) GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public double RadiusVariation
{
get { return (double) GetValue(RadiusVariationProperty); }
set { SetValue(RadiusVariationProperty, value); }
}
public Color StartColor
{
get { return (Color) GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public Color EndColor
{
get { return (Color) GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
public Color StartColorVariation
{
get { return (Color) GetValue(StartColorVariationProperty); }
set { SetValue(StartColorVariationProperty, value); }
}
public Color EndColorVariation
{
get { return (Color) GetValue(EndColorVariationProperty); }
set { SetValue(EndColorVariationProperty, value); }
}
public bool BounceOffContainer { get; set; } = false;
public bool GravitateToMouse { get; set; } = false;
public double GravitateScale { get; set; } = 0.1;
public bool MouseDropsParticles { get; set; } = false;
public double MouseDropDelay
{
get { return (double) GetValue(MouseDropDelayProperty); }
set { SetValue(MouseDropDelayProperty, value); }
}
public double MouseDropDelayVariation
{
get { return (double) GetValue(MouseDropDelayVariationProperty); }
set { SetValue(MouseDropDelayVariationProperty, value); }
}
public int ClickBurstSize
{
get { return (int) GetValue(ClickBurstSizeProperty); }
set { SetValue(ClickBurstSizeProperty, value); }
}
#endregion
}
SonicEffect:
//uielement
//visual operations
public class SonicEffect : OverlayRenderDecorator
{
protected override void OnAttachChild(UIElement child)
{
child.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
}
protected override void OnDetachChild(UIElement child)
{
CompositionTarget.Rendering -= OnFrameCallback;
child.PreviewMouseLeftButtonUp -= OnMouseLeftButtonUp;
_timeTracker = null;
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_timeTracker != null)
{
_timeTracker.TimerFired -= OnTimerFired;
_timeTracker = null;
}
CompositionTarget.Rendering += OnFrameCallback;
_timeTracker = new ParticleEffectsTimeTracker {TimerInterval = _ringDelayInSeconds};
_timeTracker.TimerFired += OnTimerFired;
_lowerRing = _upperRing = 0;
_clickPosition = e.GetPosition(this);
}
private void OnFrameCallback(object sender, EventArgs e)
{
if (_timeTracker != null)
{
_timeTracker.Update();
InvalidateVisual();
}
}
private void OnTimerFired(object sender, EventArgs e)
{
if (_upperRing < RingCount)
{
_upperRing++;
}
else
{
_lowerRing++;
if (_lowerRing >= _upperRing)
{
_timeTracker.TimerFired -= OnTimerFired;
_timeTracker = null;
CompositionTarget.Rendering -= OnFrameCallback;
}
}
}
protected override void OnOverlayRender(DrawingContext dc)
{
if (_timeTracker != null)
{
for (var i = _lowerRing; i < _upperRing; i++)
{
var radius = RingRadius*(i + 1);
dc.DrawEllipse(Brushes.Transparent, new Pen(new SolidColorBrush(RingColor), RingThickness),
_clickPosition, radius, radius);
}
}
}
#region Private Members
private ParticleEffectsTimeTracker _timeTracker;
private double _ringDelayInSeconds = 3;
private Point _clickPosition;
private int _lowerRing, _upperRing;
#endregion
#region Properties
public int RingCount { get; set; } = 5;
public TimeSpan RingDelay
{
get { return TimeSpan.FromSeconds(_ringDelayInSeconds); }
set { _ringDelayInSeconds = value.TotalSeconds; }
}
public double RingRadius { get; set; } = 7.0;
public double RingThickness { get; set; } = 5.0;
public Color RingColor { get; set; } = Color.FromArgb(128, 128, 128, 128);
#endregion
}
MagnitismCanvas:
public class MagnitismCanvas : Canvas
{
public MagnitismCanvas()
{
// suppress movement in the visual studio designer.
if (Process.GetCurrentProcess().ProcessName != "devenv")
CompositionTarget.Rendering += UpdateChildren;
_timeTracker = new ParticleEffectsTimeTracker();
}
private void UpdateChildren(object sender, EventArgs e)
{
//update time delta
_timeTracker.Update();
foreach (UIElement child in LogicalTreeHelper.GetChildren(this))
{
var velocity = _childrenVelocities.ContainsKey(child) ? _childrenVelocities[child] : new Vector(0, 0);
//compute velocity dampening
velocity = velocity*Drag;
var truePosition = GetTruePosition(child);
var childRect = new Rect(truePosition, child.RenderSize);
//accumulate forces
var forces = new Vector(0, 0);
//add wall repulsion
forces.X += BorderForce/Math.Max(1, childRect.Left);
forces.X -= BorderForce/Math.Max(1, RenderSize.Width - childRect.Right);
forces.Y += BorderForce/Math.Max(1, childRect.Top);
forces.Y -= BorderForce/Math.Max(1, RenderSize.Height - childRect.Bottom);
//each other child pushes away based on the square distance
foreach (UIElement otherchild in LogicalTreeHelper.GetChildren(this))
{
//dont push against itself
if (otherchild == child)
continue;
var otherchildtruePosition = GetTruePosition(otherchild);
var otherchildRect = new Rect(otherchildtruePosition, otherchild.RenderSize);
//make sure rects aren't the same
if (otherchildRect == childRect)
continue;
//ignore children with a size of 0,0
if (otherchildRect.Width == 0 && otherchildRect.Height == 0 ||
childRect.Width == 0 && childRect.Height == 0)
continue;
//vector from current other child to current child
//are they overlapping? if so, distance is 0
var toChild = AreRectsOverlapping(childRect, otherchildRect)
? new Vector(0, 0)
: VectorBetweenRects(childRect, otherchildRect);
var length = toChild.Length;
if (length < 1)
{
length = 1;
var childCenter = GetCenter(childRect);
var otherchildCenter = GetCenter(otherchildRect);
//compute toChild from the center of both rects
toChild = childCenter - otherchildCenter;
}
var childpush = ChildForce/length;
toChild.Normalize();
forces += toChild*childpush;
}
//add forces to velocity and store it for next iteration
velocity += forces;
_childrenVelocities[child] = velocity;
//move the object based on it's velocity
SetTruePosition(child, truePosition + _timeTracker.DeltaSeconds*velocity);
}
}
private bool AreRectsOverlapping(Rect r1, Rect r2)
{
if (r1.Bottom < r2.Top) return false;
if (r1.Top > r2.Bottom) return false;
if (r1.Right < r2.Left) return false;
if (r1.Left > r2.Right) return false;
return true;
}
private Point IntersectInsideRect(Rect r, Point raystart, Vector raydir)
{
var xtop = raystart.X + raydir.X*(r.Top - raystart.Y)/raydir.Y;
var xbottom = raystart.X + raydir.X*(r.Bottom - raystart.Y)/raydir.Y;
var yleft = raystart.Y + raydir.Y*(r.Left - raystart.X)/raydir.X;
var yright = raystart.Y + raydir.Y*(r.Right - raystart.X)/raydir.X;
var top = new Point(xtop, r.Top);
var bottom = new Point(xbottom, r.Bottom);
var left = new Point(r.Left, yleft);
var right = new Point(r.Right, yright);
var tv = raystart - top;
var bv = raystart - bottom;
var lv = raystart - left;
var rv = raystart - right;
//classify ray direction
if (raydir.Y < 0)
{
if (raydir.X < 0) //top left
{
if (tv.LengthSquared < lv.LengthSquared)
return top;
return left;
}
if (tv.LengthSquared < rv.LengthSquared)
return top;
return right;
}
if (raydir.X < 0) //bottom left
{
if (bv.LengthSquared < lv.LengthSquared)
return bottom;
return left;
}
if (bv.LengthSquared < rv.LengthSquared)
return bottom;
return right;
}
private Vector VectorBetweenRects(Rect r1, Rect r2)
{
//find the edge points and use these to measure the distance
var r1Center = GetCenter(r1);
var r2Center = GetCenter(r2);
var between = (r1Center - r2Center);
between.Normalize();
var edge1 = IntersectInsideRect(r1, r1Center, -between);
var edge2 = IntersectInsideRect(r2, r2Center, between);
return edge1 - edge2;
}
private Point GetRenderTransformOffset(UIElement e)
{
//make sure they object's render transform is a translation
var renderTranslation = e.RenderTransform as TranslateTransform;
if (renderTranslation == null)
{
renderTranslation = new TranslateTransform(0, 0);
e.RenderTransform = renderTranslation;
}
return new Point(renderTranslation.X, renderTranslation.Y);
}
private void SetRenderTransformOffset(UIElement e, Point offset)
{
//make sure they object's render transform is a translation
var renderTranslation = e.RenderTransform as TranslateTransform;
if (renderTranslation == null)
{
renderTranslation = new TranslateTransform(0, 0);
e.RenderTransform = renderTranslation;
}
//set new offset
renderTranslation.X = offset.X;
renderTranslation.Y = offset.Y;
}
private Point GetTruePosition(UIElement e)
{
var renderTranslation = GetRenderTransformOffset(e);
return new Point(GetLeft(e) + renderTranslation.X, GetTop(e) + renderTranslation.Y);
}
private void SetTruePosition(UIElement e, Point p)
{
var canvasOffset = new Vector(GetLeft(e), GetTop(e));
var renderTranslation = p - canvasOffset;
SetRenderTransformOffset(e, renderTranslation);
}
private Point GetCenter(Rect r) => new Point((r.Left + r.Right) / 2.0, (r.Top + r.Bottom) / 2.0);
#region Private Members
private readonly ParticleEffectsTimeTracker _timeTracker;
private readonly Dictionary<UIElement, Vector> _childrenVelocities = new Dictionary<UIElement, Vector>();
#endregion
#region Properties
public double BorderForce { get; set; } = 1000.0;
public double ChildForce { get; set; } = 200.0;
public double Drag { get; set; } = 0.9;
#endregion
}
ParticleEffectsTimeTracker:
//uielement
//visual operations
//animation effect stuff
public class ParticleEffectsTimeTracker
{
#region Constructors
public ParticleEffectsTimeTracker()
{
ElapsedTime = DateTime.Now;
}
#endregion
#region Events
public event EventHandler TimerFired;
#endregion
public double Update()
{
var currentTime = DateTime.Now;
//get the difference in time
var diffTime = currentTime - ElapsedTime;
DeltaSeconds = diffTime.TotalSeconds;
//does the user want a callback on regular intervals?
if (TimerInterval > 0.0)
{
/*
//compute the intervals for this and previous update
int currInterval = (int)(currentTime / TimeSpan.FromSeconds(_timerInterval));
int prevInterval = (int)(_lastTime / TimeSpan.FromSeconds(_timerInterval));
//has the interval changed since last update?
if (currInterval != prevInterval)
{
//fire interval event
//note that this will only be called once per frame at most
// so if they interval is too small, you wont get 2+ fires per frame
TimerFired(this, null);
} */
if (currentTime != ElapsedTime)
{
TimerFired?.Invoke(this, null);
}
}
//cycle old time
ElapsedTime = currentTime;
return DeltaSeconds;
}
#region Private Memebers
#endregion
#region Properties
public double TimerInterval { get; set; } = -1;
public DateTime ElapsedTime { get; private set; }
public double DeltaSeconds { get; private set; }
#endregion
}
Particle:
internal class Particle
{
public DateTime DeathTime;
public double Diameter;
public Color EndColor;
public DateTime LifeTime;
public Point Location;
public Color StartColor;
public Vector Velocity;
}
代码解析:
-
OverlayRenderDecoratorOverlayVisual属性IsHitTestVisible 为什么设置为False?
- 当为True时,点击一次出现微粒爆发,此时再点击时会执行微粒上的点击命中并阻止进行下一层visual命中测试,因此按钮不会触发点击事件。
- 当为false时,微粒visual层不触发命中测试,直接进行下一层按钮的命中测试,因此会执行按钮点击事件,并会再次爆发出微粒。
3.此属性受继承类的IsOverlayHitTestVisible属性关联开关管理。
后续补充详细---
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。