開篇!WPF自定義控件(1)——轉盤菜單


注冊園子賬號有半年了吧,一直想寫點什么,但不知道從哪寫起。最近一個月在無錫一個公司實習,主要做的就是WPF相關的開發,雖然之前沒接觸過WPF,但好歹學過C#,上手理解起來還算容易。

最近做了個自定義圓形轉盤菜單控件,效果如下:

實現過程:

  1. 圓周平分孩子節點(這里孩子節點放的都是圖標)
  2. 實現點擊中間圖標,外圍圖標旋轉一周動畫
  3. 實現鼠標點擊拖動跟隨旋轉動畫

關鍵代碼:

  1.圓周平分孩子節點。先遍歷所有孩子節點,再減去中間圖標,得到要平分圓周的圖標個數,然后得到平分角度,最后根據角度、外圓半徑,利用三角函數設置每個圖標的TopPropertyLeftProperty屬性

 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源碼:旋轉菜單控件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM