注册园子账号有半年了吧,一直想写点什么,但不知道从哪写起。最近一个月在无锡一个公司实习,主要做的就是WPF相关的开发,虽然之前没接触过WPF,但好歹学过C#,上手理解起来还算容易。
最近做了个自定义圆形转盘菜单控件,效果如下:
实现过程:
- 圆周平分孩子节点(这里孩子节点放的都是图标)
- 实现点击中间图标,外围图标旋转一周动画
- 实现鼠标点击拖动跟随旋转动画
关键代码:
1.圆周平分孩子节点。先遍历所有孩子节点,再减去中间图标,得到要平分圆周的图标个数,然后得到平分角度,最后根据角度、外圆半径,利用三角函数设置每个图标的TopProperty和LeftProperty属性
1 //等分圆周 显示中间和周围图标 2 private void DivideCircleOnShowMenu() 3 { 4 //获取孩子节点 5 UIElementCollection children = this.Children; 6 FrameworkElement f; 7 8 //外圆半径 9 double outCircleRadius = this.Width/2-IconWidthAndHeight/2; 10 11 //平分度数 12 double divideEquallyAngle = 360 / (children.Count - 1); 13 14 for (int i = 0; i < children.Count; i++) 15 { 16 f = children[i] as FrameworkElement; 17 //第一个中间图标 18 if (i == 0) 19 { 20 if (ShowMenu) 21 { 22 f.SetValue(Canvas.TopProperty, outCircleRadius ); 23 f.SetValue(Canvas.LeftProperty, outCircleRadius ); 24 if (OperateType == OperateEnum.MouseOperate) 25 { 26 f.MouseDown += F_MouseDown; 27 } 28 else 29 { 30 f.TouchUp += F_TouchUp; 31 } 32 } 33 34 else 35 { 36 f.SetValue(Canvas.TopProperty, outCircleRadius - f.Width / 2); 37 f.SetValue(Canvas.LeftProperty, outCircleRadius - f.Width / 2); 38 f.Visibility = Visibility.Hidden; 39 } 40 } 41 42 else 43 { 44 //内角度数 角度转换为弧度 45 double innerAngle = divideEquallyAngle * (i-1) * Math.PI / 180; 46 47 //TOP距离 48 double topHeight = outCircleRadius - Math.Cos(innerAngle) * outCircleRadius; 49 50 //Left距离 51 double leftWidth = Math.Sin(innerAngle) * outCircleRadius; 52 53 if (innerAngle <= 180) 54 { 55 f.SetValue(Canvas.TopProperty, topHeight ); 56 f.SetValue(Canvas.LeftProperty, outCircleRadius + leftWidth ); 57 } 58 if (innerAngle > 180) 59 { 60 f.SetValue(Canvas.TopProperty, topHeight); 61 f.SetValue(Canvas.LeftProperty, outCircleRadius - leftWidth ); 62 } 63 } 64 } 65 }
2.点击中间图标,外围图标旋转一周动画。这里用到的是RotateTransform动画,动画很简单,就是绕点旋转。但是还得设置每个图标的RenderTransformOrigin,这个属性是一个相对Point,以每个图标的左上角为坐标原点,向下和向右为正,比如设为(0.5,0.5)表示每个图标绕本身的中心点旋转,(1,1)表示绕本身的右下角旋转。这里我是选择让整个canvas绕着自身中心点旋转,然后再让每个图标绕自身反转,就可以实现图标方向相对不变。最后我这里rotateNumber(旋转度数)是自定义的依赖属性,可以在前台XAML应用这个控件时自行设置的。
1 //canvans旋转 2 private void RotateAnimation(UIElement uIelement, double rotateNumber) 3 { 4 RotateTransform rtf = new RotateTransform(); 5 uIelement.RenderTransform = rtf; 6 uIelement.RenderTransformOrigin = new Point(0.5, 0.5); 7 8 //定义动画路径和事件 9 DoubleAnimation dbAnimation = new DoubleAnimation(0, rotateNumber, 10 new Duration(TimeSpan.FromSeconds(RotateSpeed))); 11 12 //开始动画 13 rtf.BeginAnimation(RotateTransform.AngleProperty, dbAnimation);
3.实现鼠标拖动跟随旋转动画。这里主要就是MouseDown,MouseMove和MouseUp事件。主要是MouseMove中要获取到每次移动旋转的度数,以及转动方向的判断(顺时针转还是逆时针转)。求每次拖动旋转的度数我用到了高中的数学知识,两向量夹角的余弦公式(没错,就是它了,这也是我目前能想到的办法了= =)。根据这个公式算出旋转度数后,就要开始根据每次点击点和结束点的象限来判断旋转方向。
//拖动图标旋转事件 private void Rotate_MouseMove(object sender, MouseEventArgs e) { Point mAfter = e.GetPosition(this); //获取鼠标移动过程中的坐标 Point n1 = new Point(centerP.X - mBefore.X, centerP.Y - mBefore.Y); Point n2 = new Point(centerP.X - mAfter.X, centerP.Y - mAfter.Y); //n1*n2 double n1n2 = n1.X * n2.X + n1.Y * n2.Y; //n1的模 double n1mo = Math.Sqrt(Math.Pow(n1.X, 2) + Math.Pow(n1.Y, 2)); //n2的模 double n2mo = Math.Sqrt(Math.Pow(n2.X, 2) + Math.Pow(n2.Y, 2)); //得带旋转角度 double rotateNum = Math.Acos(n1n2 / (n1mo * n2mo)); //相对坐标原点位置 Point potM = new Point(); potM.X = mAfter.X - centerP.X; potM.Y = centerP.Y - mAfter.Y; Point potD = new Point(); potD.X = mBefore.X - centerP.X; potD.Y = centerP.Y - mBefore.Y; //当鼠标移动超出边界时停止旋转 if (mAfter.X < 0 || mAfter.X > this.Width || mAfter.Y < 0 || mAfter.Y > this.Height) { this.MouseMove -= Rotate_MouseMove; } else { if (GetcLockwise(potD, potM)) { rotateAng += rotateNum; } else { rotateAng -= rotateNum; } } //执行旋转动画 IconRotateAnimation(-rotateAng); CanvansRotateAnimation(rotateAng); } /// <summary> /// 获取顺时针还是逆时针 /// </summary> /// <param name="potD">按下坐标</param> /// <param name="potM">移动坐标</param> /// <returns>True:顺,False:逆</returns> private bool GetcLockwise(Point potD, Point potM) { if (potM.Y >= 0 && potD.Y >= 0) //一二象限 { return potM.X >= potD.X; } if (potM.Y < 0 && potD.Y < 0) //三四象限 { return potM.X <= potD.X; } if (potM.X >= 0 && potD.X >= 0) //一四象限 { return potM.Y <= potD.Y; } if (potM.X < 0 && potD.X < 0) //二三象限 { return potM.Y >= potD.Y; } else { return true; } }
最后,又增加了触摸滑动功能,其实大体上是就是把对应的Mouse事件换成Touch事件就OK了,但效果实现上可能需要略微的改动。本来还要做成旋转带惯性的效果的,只可惜太菜,网上资源也比较少,只能无告而终。现在觉得做WPF的控件开发也蛮有意思的,蛮锻炼人的逻辑思维能力的。开篇文到此为止了,再接再励吧!
附上Demo源码:旋转菜单控件