这一篇偏向于逻辑的比较多,放在这个系列里会不会欠妥呢?在中国交互性设计也是美工的份内职责哦~
所以没有blend基础的人也可以看懂这篇文章,不过要用到初中的几何知识哦~亲
相信很多人都在手机或者网页上或者KTV的点歌系统里看到过旋转木马的目录导航,这个是如何做的呢??
最终效果如下:(貌似有点太大了显示不下,附加个阅览地址:
)
(特别鸣谢烤地瓜的答疑,和地瓜村众人的热心帮助)
1.总体思路
分析上面效果:一排方块在转圈,点击的块跑到最近的位置
圈:其实就是一个椭圆,只不过人的近大远小的逻辑思维,大脑根据常识把它装换成了一个空间。
最近的位置:其实就是椭圆的最下面,块变得最大,所以感觉最近。
2.设计过程
首先我们来实现让这堆块围绕成一个椭圆。
step.1 椭圆是这样来产生滴~!↓
我以我微薄的几何知识用blend画出了上图,右上角看到我们久违的椭圆公式。第一个公式当把y单独提到等号一边时发现是要开根号的这样,就如图所看到的,一个x对应着两个y值还得去判断正负号显得麻烦了,就用第二个公式吧。其中a为x半轴,b为y半轴,d为角度。只需要一个角度d就可以确定出这个椭圆中的所有点了。
首先写出变换块位置的方法:
private void SetPosition(Grid item, double degree) { TransformGroup myTG = item.RenderTransform as TransformGroup; TranslateTransform myTT = myTG.Children[0] as TranslateTransform;//位置变换 myTT.X = a * Math.Cos(degree);//计算x坐标 myTT.Y = b * Math.Sin(degree);//计算y坐标 item.Tag = degree; ScaleTransform myST = myTG.Children[1] as ScaleTransform;//大小变换 myST.ScaleX = myST.ScaleY = (1 - scale) / b * myTT.Y + scale; Canvas.SetZIndex(item, (int)(myTT.Y + 2 * b));//层次变换为Y轴的位置 }
step.2 跟随鼠转标起来吧
这样传入需要变换的块和事先计算的该块的位置所在就角度就可以确定该块的位置、大小、层次了。单mousemove的时候,只要给每一个块都重新定位,把鼠标的移动距离装换为块组需要旋转的角度。
private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)//鼠标移动时转动 { if (isPress)//如果当前为按住鼠标的状态 { foreach (Grid item in LayoutRoot.Children)//遍历所有的Grid { SetPosition(item, (double)item.Tag + (startPoint.X - e.GetPosition(this).X) * 0.005);//变换位置 } startPoint.X = e.GetPosition(this).X;//把但前位置赋给开始位置 } }
我把每个Grid的Tag用来存储自己当前所在的角度。
step.3 点击就转到前面来啊,亲~
接下来制作点击块转动的动画,即点击块后更具该块所在的位置,计算出旋转到90'时需要旋转的角度,然后所有块都转这个角度(为什么是90°呢?应该是270°啊~因为wpf中的位移动画,Y轴移动向下是为正,向上为负,这样就刚好和我们课堂上通用的坐标系刚好上下翻转了)
void newGrid_MouseUp(object sender, MouseButtonEventArgs e)//单击其中一块转正 { double nowAngle = (double)((sender as Grid).Tag);//根据当前的块的角度 double needAngle = Math.PI / 2 - nowAngle % (Math.PI * 2);//换算出把改块转正整体需要转动的角度 foreach (Grid item in LayoutRoot.Children)//为每一个块都添加动画 { degree = (double)item.Tag;//获取各个块的角度 TransformGroup myTG = item.RenderTransform as TransformGroup; TranslateTransform myTT = myTG.Children[0] as TranslateTransform;//位置变换 Storyboard sb = new Storyboard(); sb.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); DoubleAnimation xAnimation = new DoubleAnimation(); xAnimation.To = a * Math.Cos(degree + needAngle);//计算x坐标 xAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); sb.Children.Add(xAnimation); Storyboard.SetTarget(xAnimation, myTT); Storyboard.SetTargetProperty(xAnimation, new PropertyPath("X")); DoubleAnimation yAnimation = new DoubleAnimation(); yAnimation.To = b * Math.Sin(degree + needAngle);//计算x坐标 yAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); sb.Children.Add(yAnimation); Storyboard.SetTarget(yAnimation, myTT); Storyboard.SetTargetProperty(yAnimation, new PropertyPath("Y")); ScaleTransform myST = myTG.Children[1] as ScaleTransform;//大小变换 DoubleAnimation xScaleAnimation = new DoubleAnimation(); DoubleAnimation yScaleAnimation = new DoubleAnimation(); xScaleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); yScaleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); if (LayoutRoot.Children.IndexOf(item) == 0)//只添加一次动画完成后遍历控件改变位置 { yScaleAnimation.Completed += new EventHandler((s, events) =>//动画结束后把整体的位置调整为动画结束时的位置 { foreach (Grid item1 in LayoutRoot.Children) { SetPosition(item1, (double)item1.Tag); } }); } yScaleAnimation.To = xScaleAnimation.To = (1 - scale) / b * yAnimation.To + scale; sb.Children.Add(xScaleAnimation); Storyboard.SetTarget(xScaleAnimation, myST); Storyboard.SetTargetProperty(xScaleAnimation, new PropertyPath("ScaleX")); sb.Children.Add(yScaleAnimation); Storyboard.SetTarget(yScaleAnimation, myST); Storyboard.SetTargetProperty(yScaleAnimation, new PropertyPath("ScaleY")); sb.Begin();//开始动画 item.Tag = degree + needAngle;//记录最后的角度 } }
step.4 最靠近下面的要自动对正哦~
OK,接下来还差最有一点就是自动对正了,即当拖动时把最接近最近位置的块自定定位到最近。
原理就是遍历所有块的但前位置,角度越接近90°的就模拟一下它的单击
private void LayoutRoot_MouseUp(object sender, MouseButtonEventArgs e)//当鼠标弹起时判断最近的块,自动转正 { double minNear = 100; Grid nearGrid = null; foreach (Grid item in LayoutRoot.Children)//找出最近的Grid { double near = Math.Abs(Math.PI / 2 - (double)item.Tag % (Math.PI * 2)); if (near < minNear) { minNear = near; nearGrid = item; } } newGrid_MouseUp(nearGrid, null);//模拟最近的块被点了一次 isPress = false; }
做完收工。
下面是我修改左上角的参数实现的几个比较好看的效果
效率也是很不错的,上面的100个块在我i7的电脑上一点都不卡哦。
大家如果调出什么好看的效果可以贴到回复里,交流下。
我把核心代码都讲了遍,如果还是有不懂的,可以给我留言。当然了这个silverlight版只是作为演示用得第一版,有很多细节方面我没处理。至于源码,因为我做的这个原版是WPF程序作为商用,所以经过多番考虑我还是不公布源码了。当然了我是不会告诉你可以反编译.xap来获取源码的。