WPF仿3D照片墙旋转动画Demo

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

    }
}

运行效果

WPF仿3D照片墙旋转动画Demo_第1张图片

完整Demo下载:WPF3D照片墙Demo

你可能感兴趣的:(WPF仿3D照片墙旋转动画Demo)