注冊園子賬號有半年了吧,一直想寫點什么,但不知道從哪寫起。最近一個月在無錫一個公司實習,主要做的就是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源碼:旋轉菜單控件