Demo中实现两种方式旋转3照片墙动画,分别是:计时器旋转和WPF动画旋转。
ps:WPF动画旋转角度计算还需优化,Demo只是提供思路,借鉴和学习。
1.计时器旋转
XAML:
后代代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace System.Windows.Controls {
///
/// PhotoWallControl.xaml 的交互逻辑
///
public partial class PhotoWallControl : UserControl {
#region 字段
///
/// 旋转计时器
///
DispatcherTimer timer;
///
/// 椭圆中心点X
///
private double centerX;
///
/// 椭圆中心点Y
///
private double centerY;
///
/// 椭圆长轴半径
///
private double a;
///
/// 椭圆短轴半径
///
private double b;
#endregion
#region 属性
///
/// 图片数据源
///
public IEnumerable ItemsSource { get; set; }
///
/// 图片的尺寸
///
public Size ImageSize { get; set; }
///
/// 旋转动画时间(毫秒)
///
public double Duration { get; set; }
#endregion
#region 私有方法
///
/// 清空界面展示照片
///
private void ClearVirtualPhotos() {
for (int i = this.canvas.Children.Count - 1; i >= 0; i--) {
if (this.canvas.Children[i] is VirtualImage)
this.canvas.Children.RemoveAt(i);
}
}
///
/// 加载界面展示照片
///
private void LoadVirtualPhotos() {
if (this.Width == double.NaN || this.Height == double.NaN)
throw new ArgumentException("请显示设置控件的宽高!");
//设置圆心x,y
this.centerX = this.Width / 2;
this.centerY = this.Height / 2 - 0;//减去适当的值,因为会设置最下面的图片最大,上面的图片较小
//设置椭圆的长边和短边
this.a = centerX - this.ImageSize.Width / 2.0;
this.b = centerY - this.ImageSize.Height / 2.0;
//每个图片之间相隔的角度
var angle = (360.0 / this.ItemsSource.Count());
//与X轴正相交角度,初始值正下方为90°
double rotatAngle = 90;
//说明:以最下方正图为起始点,顺时针摆放图片
//注1:centerX因为默认wpf默认左上角为坐标原点
//注2:this.ImageSize / 2,是以图片中心点运动轨迹
//注3:椭圆公式:x=a*cosθ;y = b*sinθ
foreach (var virtualImage in this.ItemsSource) {
//x,y为图片在Canvas中的坐标
double x = a * Math.Cos(rotatAngle * Math.PI / 180.0) + centerX;
double y = b * Math.Sin(rotatAngle * Math.PI / 180.0) + centerY;
//当前图片坐标点与正下方图片坐标点的比值
double allpers = y / (centerY + b);
//比值最小不低于0.3
allpers = Math.Max(0.3, allpers);
//设置图片透明度
virtualImage.Opacity = Math.Max(allpers * 1.5, 0.4);
//设置图片尺寸
virtualImage.Size = new Size(this.ImageSize.Width * allpers, this.ImageSize.Height * allpers);
//设置图片中心点在Canvas坐标系中的位置
virtualImage.Point = new Point(x - virtualImage.Size.Width / 2, y - virtualImage.Size.Height / 2);
//记录图片在椭圆中与X轴正相交的角度
virtualImage.Angle = rotatAngle;
//向画布添加图片
this.canvas.Children.Add(virtualImage);
//注册图片点击事件
virtualImage.Click += PhotoWallControl_Click;
//下一张图片
rotatAngle += angle;
}
}
//获取点中图片旋转至正下方的旋转角度
private double GetImageRotatAngle(VirtualImage image) {
//注:由于记录坐标是以X轴延长线为起点,顺时针旋转角度。所以关于象限的计算方式以下为准。
if (image.Angle >= 0 && image.Angle <= 90) { //第二象限
return 90 - image.Angle;
}
else if (image.Angle > 90 && image.Angle < 270) { //第三、四象限
return image.Angle - 90;
}
else { //第一象限
return 360 - image.Angle + 90;
}
}
//获取旋转方向
private SweepDirection GetRotatDirection(VirtualImage image) {
if (image.Angle > 90 && image.Angle < 270)
return SweepDirection.Counterclockwise;
else
return SweepDirection.Clockwise;
}
//获取图片旋转结束后的角度
private double GetImageRotatEndAngle(VirtualImage image, double rotatAngle, SweepDirection rotatDirection) {
if (rotatDirection == SweepDirection.Clockwise) { //顺时针
return (image.Angle + rotatAngle) % 360;
}
else { //逆时针
var angle = image.Angle - rotatAngle;
while (angle < 0)
angle = 360 + angle;
return angle % 360;
}
}
///
/// 图片点击事件,旋转动画
///
void PhotoWallControl_Click(object sender, RoutedEventArgs e) {
//计算点中图片旋转到正下方旋转角度N
double rotatAngle = this.GetImageRotatAngle(sender as VirtualImage);
if (rotatAngle == 0) return;
//计算点中图片旋转方向
SweepDirection rotatDirection = this.GetRotatDirection(sender as VirtualImage);
//每次旋转角度
double everyRotatAngle = rotatAngle / this.Duration;
//当前旋转角度
double currentRotatAngle = 0.0;
//定时器,定时修改ImageInfo中各属性,从而实现动画效果
if (timer != null) timer.Stop();
timer = new DispatcherTimer();
//时间间隔
timer.Interval = TimeSpan.FromMilliseconds(1);
timer.Tick += (ss, ee) => {
//图片序号
int index = 0;
currentRotatAngle += everyRotatAngle;
//计算其余图片旋转N度后的坐标点
foreach (var virtualImage in this.ItemsSource) {
//获取旋转后的角度
double rotatEndAngle = this.GetImageRotatEndAngle(virtualImage, everyRotatAngle, rotatDirection);
if (currentRotatAngle >= rotatAngle)
rotatEndAngle -= currentRotatAngle - rotatAngle;//减去多余的旋转角度
//x=cos * a
var x = Math.Cos(rotatEndAngle * Math.PI / 180) * a + centerX;
//y=sin * b
var y = Math.Sin(rotatEndAngle * Math.PI / 180) * b + centerY;
//当前图片与最下面一张图片的Y的比值
var allpers = y / (centerY + b);
//不要小于0.3,太小了就看不见了,可以适当调整
allpers = Math.Max(0.3, allpers);
//设置尺寸
virtualImage.Size = new Size(allpers * this.ImageSize.Width, allpers * this.ImageSize.Height);
//设置坐标
virtualImage.Point = new Point(x - virtualImage.Width / 2, y - virtualImage.Height / 2);
//设置透明度
virtualImage.Opacity = Math.Max(allpers * 1.5, 0.4);
//设置角度
virtualImage.Angle = rotatEndAngle;
index++;
}
//旋转结束
if (currentRotatAngle >= rotatAngle) {
timer.Stop();
}
};
timer.Start();
}
#endregion
#region 公开方法
///
/// 照片墙实例
///
public PhotoWallControl() {
InitializeComponent();
this.ImageSize = new Size(300, 300);
this.Duration = 30;
}
///
/// 刷新数据源展示
///
public void RefreshSource() {
if (this.ItemsSource == null)
this.ClearVirtualPhotos();
else {
this.ClearVirtualPhotos();
this.LoadVirtualPhotos();
}
}
#endregion
}
///
/// 展示图片
///
public class VirtualImage : ButtonBase {
#region 依赖属性
public ImageSource Image {
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
// Using a DependencyProperty as the backing store for Image. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(VirtualImage), new PropertyMetadata(null));
#endregion
///
/// 实例化
///
public VirtualImage(string imgPath) {
if (string.IsNullOrWhiteSpace(imgPath)) return;
this.Image = new BitmapImage(new Uri(imgPath));
}
///
/// 实例化
///
public VirtualImage() : this(null) { }
///
/// 在椭圆中心点建立坐标系,以X轴延长线为起点,顺时针旋转,图片在椭圆中的角度。
///
public double Angle { get; set; }
///
/// 图片的尺寸。
///
public Size Size {
get { return new Size(base.Width, base.Height); }
set { base.Width = value.Width; base.Height = value.Height; }
}
///
/// 以Canvas建立的坐标系,图片在坐标系中坐标点。
///
public Point Point {
get { return new Point(Canvas.GetLeft(this), Canvas.GetTop(this)); }
set { Canvas.SetLeft(this, value.X); Canvas.SetTop(this, value.Y); Canvas.SetZIndex(this, (int)value.Y); }
}
}
}
2.WPF动画旋转
XAML:
后台代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace System.Windows.Controls {
///
/// PhotoWallAnimation.xaml 的交互逻辑
///
public partial class PhotoWallAnimation : UserControl {
#region 字段
///
/// 椭圆中心点X
///
private double centerX;
///
/// 椭圆中心点Y
///
private double centerY;
///
/// 椭圆长轴半径
///
private double a;
///
/// 椭圆短轴半径
///
private double b;
#endregion
#region 属性
///
/// 图片数据源
///
public IEnumerable ItemsSource { get; set; }
///
/// 图片的尺寸
///
public Size ImageSize { get; set; }
#endregion
#region 私有方法
///
/// 清空界面展示照片
///
private void ClearVirtualPhotos() {
for (int i = this.canvas.Children.Count - 1; i >= 0; i--) {
if (this.canvas.Children[i] is VirtualImage)
this.canvas.Children.RemoveAt(i);
}
}
///
/// 加载界面展示照片
///
private void LoadVirtualPhotos() {
if (this.Width == double.NaN || this.Height == double.NaN)
throw new ArgumentException("请显示设置控件的宽高!");
//设置圆心x,y
this.centerX = this.Width / 2;
this.centerY = this.Height / 2 - 0;//减去适当的值,因为会设置最下面的图片最大,上面的图片较小
//设置椭圆的长边和短边
this.a = centerX - this.ImageSize.Width / 2.0;
this.b = centerY - this.ImageSize.Height / 2.0;
//每个图片之间相隔的角度
var angle = (360.0 / this.ItemsSource.Count());
//与X轴正相交角度,初始值正下方为90°
double rotatAngle = 90;
//说明:以最下方正图为起始点,顺时针摆放图片
//注1:centerX因为默认wpf默认左上角为坐标原点
//注2:this.ImageSize / 2,是以图片中心点运动轨迹
//注3:椭圆公式:x=a*cosθ;y = b*sinθ
foreach (var virtualImage in this.ItemsSource) {
//x,y为图片在Canvas中的坐标
double x = a * Math.Cos(rotatAngle * Math.PI / 180.0) + centerX;
double y = b * Math.Sin(rotatAngle * Math.PI / 180.0) + centerY;
//当前图片坐标点与正下方图片坐标点的比值
double allpers = y / (centerY + b);
//比值最小不低于0.3
allpers = Math.Max(0.3, allpers);
//设置图片透明度
virtualImage.Opacity = Math.Max(allpers * 1.5, 0.4);
//设置图片尺寸
virtualImage.Size = new Size(this.ImageSize.Width * allpers, this.ImageSize.Height * allpers);
//设置图片中心点在Canvas坐标系中的位置
virtualImage.Point = new Point(x - virtualImage.Size.Width / 2, y - virtualImage.Size.Height / 2);
//记录图片在椭圆中与X轴正相交的角度
virtualImage.Angle = rotatAngle;
//向画布添加图片
this.canvas.Children.Add(virtualImage);
//注册图片点击事件
virtualImage.Click += PhotoWallControl_Click;
//下一张图片
rotatAngle += angle;
}
}
///
/// 图片点击事件,旋转动画
///
void PhotoWallControl_Click(object sender, RoutedEventArgs e) {
//计算点中图片旋转到正下方旋转角度N
double rotatAngle = this.GetImageRotatAngle(sender as VirtualImage);
if (rotatAngle == 0) return;
//计算点中图片旋转方向
SweepDirection rotatDirection = this.GetRotatDirection(sender as VirtualImage);
//定义动画故事版
Storyboard storyboard = new Storyboard();
//旋转重点,计算旋转椭圆中心点以及轴半径。
//以X轴延长线,旋转90°,计算左上角坐标点
double x90 = this.a * Math.Cos(90 * Math.PI / 180.0) + this.centerX;
double y90 = this.b * Math.Sin(90 * Math.PI / 180.0) + this.centerY;
//以X轴延长线,旋转180°,计算左上角坐标点
double x180 = this.a * Math.Cos(180 * Math.PI / 180.0) + this.centerX;
double y180 = this.b * Math.Sin(180 * Math.PI / 180.0) + this.centerY;
//以X轴延长线,旋转270°,计算左上角坐标点
double x270 = this.a * Math.Cos(270 * Math.PI / 180.0) + this.centerX;
double y270 = this.b * Math.Sin(270 * Math.PI / 180.0) + this.centerY;
//以X轴延长线,旋转360°,计算左上角坐标点
double x360 = this.a * Math.Cos(360 * Math.PI / 180.0) + this.centerX;
double y360 = this.b * Math.Sin(360 * Math.PI / 180.0) + this.centerY;
//计算旋转后比值
double allpers90 = y90 / y90;
double allpers180 = y180 / y90;
double allpers270 = y270 / y90;
double allpers360 = y360 / y90;
Size imageSize90 = new Size(this.ImageSize.Width * allpers90, this.ImageSize.Height * allpers90);
Size imageSize180 = new Size(this.ImageSize.Width * allpers180, this.ImageSize.Height * allpers180);
Size imageSize270 = new Size(this.ImageSize.Width * allpers270, this.ImageSize.Height * allpers270);
Size imageSize360 = new Size(this.ImageSize.Width * allpers360, this.ImageSize.Height * allpers360);
Point imagePoint90 = new Point(x90 - imageSize90.Width / 2, y90 - imageSize90.Height / 2);
Point imagePoint180 = new Point(x180 - imageSize180.Width / 2, y180 - imageSize180.Height / 2);
Point imagePoint270 = new Point(x270 - imageSize270.Width / 2, y270 - imageSize270.Height / 2);
Point imagePoint360 = new Point(x360 - imageSize360.Width / 2, y360 - imageSize360.Height / 2);
//已知椭圆4个远端点,可计算得出椭圆的中心点以及半径
double rotatA = (imagePoint360.X - imagePoint180.X) / 2;
double rotatB = (imagePoint90.Y - imagePoint270.Y) / 2;
//double rotatCenterX = x180 + rotatA;
//double rotatCenterY = y270 + rotatB;
//计算其余图片旋转N度后的坐标点
foreach (var image in this.ItemsSource) {
//获取旋转后的角度
double rotatEndAngle = this.GetImageRotatEndAngle(image, rotatAngle, rotatDirection);
//x,y为图片左上角在Canvas中的坐标
double x = this.a * Math.Cos(rotatEndAngle * Math.PI / 180.0) + this.centerX;
double y = this.b * Math.Sin(rotatEndAngle * Math.PI / 180.0) + this.centerY;
//当前图片坐标点与正下方图片坐标点的比值
double allpers = y / (this.centerY + this.b);
//比值最小不低于0.3
allpers = Math.Max(0.3, allpers);
//旋转结束图片透明度
double endOpacity = Math.Max(allpers * 1.5, 0.4);
//旋转结束图片尺寸
Size endImageSize = new Size(this.ImageSize.Width * allpers, this.ImageSize.Height * allpers);
//旋转结束图片中心点在Canvas坐标系中的位置
Point endImagePoint = new Point(x - endImageSize.Width / 2, y - endImageSize.Height / 2);
//记录图片在椭圆中与X轴正相交的角度
image.Angle = rotatEndAngle;
//准备动画
var duration = new Duration(TimeSpan.FromSeconds(0.5));
//旋转动画
ArcSegment arcX = new ArcSegment(endImagePoint, new Size(rotatA, rotatB), 0, false, rotatDirection, true);
ArcSegment arcY = new ArcSegment(endImagePoint, new Size(rotatA, rotatB), 0, false, rotatDirection, true);
PathFigure figureX = new PathFigure() { StartPoint = image.Point, Segments = new PathSegmentCollection() { arcX } };
PathFigure figureY = new PathFigure() { StartPoint = image.Point, Segments = new PathSegmentCollection() { arcY } };
DoubleAnimationUsingPath xAnimation = new DoubleAnimationUsingPath() {
Duration = duration,
Source = PathAnimationSource.X,
PathGeometry = new PathGeometry() { Figures = new PathFigureCollection(new PathFigure[] { figureX }) }
};
DoubleAnimationUsingPath yAnimation = new DoubleAnimationUsingPath() {
Duration = duration,
Source = PathAnimationSource.Y,
PathGeometry = new PathGeometry() { Figures = new PathFigureCollection(new PathFigure[] { figureY }) }
};
Storyboard.SetTarget(xAnimation, image);
Storyboard.SetTarget(yAnimation, image);
Storyboard.SetTargetProperty(xAnimation, new PropertyPath(Canvas.LeftProperty));
Storyboard.SetTargetProperty(yAnimation, new PropertyPath(Canvas.TopProperty));
//图片置顶
var zIndexAnimation = new Int32Animation((int)image.Point.Y, (int)endImagePoint.Y, duration);
Storyboard.SetTarget(zIndexAnimation, image);
Storyboard.SetTargetProperty(zIndexAnimation, new PropertyPath(Canvas.ZIndexProperty));
//透明度动画
var opacityAnimation = new DoubleAnimation(image.Opacity, endOpacity, duration);
Storyboard.SetTarget(opacityAnimation, image);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(FrameworkElement.OpacityProperty));
//尺寸动画
var widthAnimation = new DoubleAnimation(image.Width, endImageSize.Width, duration);
var heightAnimation = new DoubleAnimation(image.Height, endImageSize.Height, duration);
Storyboard.SetTarget(widthAnimation, image);
Storyboard.SetTarget(heightAnimation, image);
Storyboard.SetTargetProperty(widthAnimation, new PropertyPath(FrameworkElement.WidthProperty));
Storyboard.SetTargetProperty(heightAnimation, new PropertyPath(FrameworkElement.HeightProperty));
//添加故事版
storyboard.Children.Add(xAnimation);
storyboard.Children.Add(yAnimation);
storyboard.Children.Add(widthAnimation);
storyboard.Children.Add(heightAnimation);
storyboard.Children.Add(opacityAnimation);
storyboard.Children.Add(zIndexAnimation);
//动画结束后清除画板,不然属性无法设置。
xAnimation.Completed += (ss, ee) => {
image.BeginAnimation(Canvas.LeftProperty, null);
Canvas.SetLeft(image, endImagePoint.X);
};
yAnimation.Completed += (ss, ee) => {
image.BeginAnimation(Canvas.TopProperty, null);
Canvas.SetTop(image, endImagePoint.Y);
};
zIndexAnimation.Completed += (ss, ee) => {
image.BeginAnimation(Canvas.ZIndexProperty, null);
Canvas.SetZIndex(image, (int)endImagePoint.Y);
};
opacityAnimation.Completed += (ss, ee) => {
image.BeginAnimation(FrameworkElement.OpacityProperty, null);
image.Opacity = endOpacity;
};
widthAnimation.Completed += (ss, ee) => {
image.BeginAnimation(FrameworkElement.WidthProperty, null);
image.Width = endImageSize.Width;
};
heightAnimation.Completed += (ss, ee) => {
image.BeginAnimation(FrameworkElement.HeightProperty, null);
image.Height = endImageSize.Height;
};
}
//所有图片启动动画
storyboard.Begin();
}
//获取点中图片旋转至正下方的旋转角度
private double GetImageRotatAngle(VirtualImage image) {
//注:由于记录坐标是以X轴延长线为起点,顺时针旋转角度。所以关于象限的计算方式以下为准。
if (image.Angle >= 0 && image.Angle <= 90) { //第二象限
return 90 - image.Angle;
}
else if (image.Angle > 90 && image.Angle < 270) { //第三、四象限
return image.Angle - 90;
}
else { //第一象限
return 360 - image.Angle + 90;
}
}
//获取旋转方向
private SweepDirection GetRotatDirection(VirtualImage image) {
if (image.Angle > 90 && image.Angle < 270)
return SweepDirection.Counterclockwise;
else
return SweepDirection.Clockwise;
}
//获取图片旋转结束后的角度
private double GetImageRotatEndAngle(VirtualImage image, double rotatAngle, SweepDirection rotatDirection) {
if (rotatDirection == SweepDirection.Clockwise) { //顺时针
return (image.Angle + rotatAngle) % 360;
}
else { //逆时针
var angle = image.Angle - rotatAngle;
while (angle < 0)
angle = 360 + angle;
return angle % 360;
}
}
#endregion
#region 公开方法
///
/// 照片墙实例
///
public PhotoWallAnimation() {
InitializeComponent();
this.ImageSize = new Size(300, 300);
}
///
/// 刷新数据源展示
///
public void RefreshSource() {
if (this.ItemsSource == null)
this.ClearVirtualPhotos();
else {
this.ClearVirtualPhotos();
this.LoadVirtualPhotos();
}
}
#endregion
}
}
运行效果
完整Demo下载:WPF3D照片墙Demo