效果圖 相關知識 這部分基本就是廢話,網上都能找到,我只不過是整理了以下.建議先不看,用到的時候可以回來看看 先來看兩組圖,有助於理解什麼是貝塞爾曲線(圖片取自維基百科,參考鏈接1) 二次貝塞爾曲線: P0是起點,P2是終點,P1是控制點 三次貝塞爾曲線: P0是起點,P2是終點,P1是控制點1,P ...
效果圖
相關知識
這部分基本就是廢話,網上都能找到,我只不過是整理了以下.建議先不看,用到的時候可以回來看看
貝塞爾曲線
先來看兩組圖,有助於理解什麼是貝塞爾曲線(圖片取自維基百科,參考鏈接1)
二次貝塞爾曲線:
P0是起點,P2是終點,P1是控制點
三次貝塞爾曲線:
P0是起點,P2是終點,P1是控制點1,P2是控制點2
依次連接所有點,組成線段
t是比例,在0-1之間,就是每條線段的長度都是1
貝塞爾曲線就是最裡層的線段在t位置的點所組成的路徑
三次貝塞爾曲線公式:B(t)=(1-t)^3*P0+3(1-t)^2*t*P1+3(1-t)*t^2*P2+t^3*P3,0<=t<=1
B(t)代表曲線上任意點,P0,1,2,3分別代表決定曲線的4個點,t代表曲線長度為1的任意取值
其他知識
沒接觸過貝塞爾曲線的話,可能得花些時間整理下,其他的知識就比較簡單了
直角三角形,角A的對邊a,臨邊b,斜邊c
三角函數:
sinA=a/c
cosA=b/c
勾股定理:
c^2=a^2+b^2
概括介紹
這個效果難點就兩部分:一是水球分離和融合時候的連接,二是主體圓的抖動
然而其實網上都有解決方案了
第一部分是在兩個圓之間加個用貝塞爾曲線組成的path,用一樣的顏色,其實是障眼法.見參考鏈接4
第二部分是用4段三次貝塞爾曲線組成的path代替Ellipse,因為Ellipse是抖動不起來的,這樣就可以控制貝塞爾曲線的點來讓圓抖動.見參考鏈接3
主體的大圓
Path畫法
主體的大圓是個ToggleButton,替換模版,背景換成貝塞爾曲線組成的圓.
每個貝塞爾曲線的起點和終點就不說了,非常簡單,這裡主要說說控制點.
計算出1/4圓弧的中間位置的點,此時t=0.5, 三角型邊長h=sin45*r
讓控制點P1,P2分別在起點和終點的切線上,P1到X軸的距離等於P2到Y軸的距離L
B(0.5)=h=sin45*r=(1-0.5)^3*0+3*(1-0.5)^2*0.5*L+3*(1-0.5)*0.5^2*r+0.5^3*r
sin45*r=0+0.375*L+0.375*r+0.125*r
L=(sin45*r-0.5*r)/0.375
於是兩個控制點(r,L)和(L,r)可以確定
求出來的兩個點是數學的坐標,要轉換成程式的坐標,對應成4個象限,無非就是加減半徑,加減L,不細說了
完整的path,取半徑是50,見代碼
1 <Path> 2 <Path.Data> 3 <PathGeometry> 4 <PathFigure StartPoint="50,0"> 5 <BezierSegment Point1="77.614237491541,0" Point2="100,22.385762508459" Point3="100,50"></BezierSegment> 6 <BezierSegment Point1="100,77.614237491541" Point2="77.614237491541,100" Point3="50,100"></BezierSegment> 7 <BezierSegment Point1="22.385762508459,100" Point2="0,77.614237491541" Point3="0,50"></BezierSegment> 8 <BezierSegment Point1="0,22.385762508459" Point2="22.385762508459,0" Point3="50,0"></BezierSegment> 9 </PathFigure> 10 </PathGeometry> 11 </Path.Data> 12 </Path>View Code
抖動動畫
由於圓是被12個點控制的,讓圓抖動,也就是對12個點做點動畫
可以用關鍵幀動畫,這樣控制的比較細緻,要註意的是,銜接的地方要平滑.我這裡做的比較簡陋,就找了一個變換後的圖形,重覆了5次.如果你有興趣,可以多做些,做的越多,動畫看起來表現力越強
這裡我並沒有去研究什麼演算法,就是簡單的在blend里,找了一些點
見代碼:
1 <EventTrigger RoutedEvent="Click"> 2 <BeginStoryboard> 3 <Storyboard> 4 <PointAnimationUsingKeyFrames Storyboard.TargetName="pf_main" Storyboard.TargetProperty="StartPoint" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 5 <EasingPointKeyFrame Value="40,0" KeyTime="0:0:0.2"></EasingPointKeyFrame> 6 </PointAnimationUsingKeyFrames> 7 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main0" Storyboard.TargetProperty="Point1" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 8 <EasingPointKeyFrame Value="68,-10" KeyTime="0:0:0.2"></EasingPointKeyFrame> 9 </PointAnimationUsingKeyFrames> 10 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main0" Storyboard.TargetProperty="Point2" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 11 <EasingPointKeyFrame Value="115,14" KeyTime="0:0:0.2"></EasingPointKeyFrame> 12 </PointAnimationUsingKeyFrames> 13 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main0" Storyboard.TargetProperty="Point3" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 14 <EasingPointKeyFrame Value="100,66" KeyTime="0:0:0.2"></EasingPointKeyFrame> 15 </PointAnimationUsingKeyFrames> 16 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main1" Storyboard.TargetProperty="Point1" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 17 <EasingPointKeyFrame Value="100,67" KeyTime="0:0:0.2"></EasingPointKeyFrame> 18 </PointAnimationUsingKeyFrames> 19 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main1" Storyboard.TargetProperty="Point2" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 20 <EasingPointKeyFrame Value="85,111" KeyTime="0:0:0.2"></EasingPointKeyFrame> 21 </PointAnimationUsingKeyFrames> 22 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main1" Storyboard.TargetProperty="Point3" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 23 <EasingPointKeyFrame Value="33,103" KeyTime="0:0:0.2"></EasingPointKeyFrame> 24 </PointAnimationUsingKeyFrames> 25 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main2" Storyboard.TargetProperty="Point1" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 26 <EasingPointKeyFrame Value="22,103" KeyTime="0:0:0.2"></EasingPointKeyFrame> 27 </PointAnimationUsingKeyFrames> 28 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main2" Storyboard.TargetProperty="Point2" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 29 <EasingPointKeyFrame Value="-15,85" KeyTime="0:0:0.2"></EasingPointKeyFrame> 30 </PointAnimationUsingKeyFrames> 31 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main2" Storyboard.TargetProperty="Point3" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 32 <EasingPointKeyFrame Value="-6,50" KeyTime="0:0:0.2"></EasingPointKeyFrame> 33 </PointAnimationUsingKeyFrames> 34 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main3" Storyboard.TargetProperty="Point1" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 35 <EasingPointKeyFrame Value="4,9" KeyTime="0:0:0.2"></EasingPointKeyFrame> 36 </PointAnimationUsingKeyFrames> 37 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main3" Storyboard.TargetProperty="Point2" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 38 <EasingPointKeyFrame Value="41,-1" KeyTime="0:0:0.2"></EasingPointKeyFrame> 39 </PointAnimationUsingKeyFrames> 40 <PointAnimationUsingKeyFrames Storyboard.TargetName="bs_main3" Storyboard.TargetProperty="Point3" BeginTime="0:0:0.7" AutoReverse="True" RepeatBehavior="5x" FillBehavior="Stop"> 41 <EasingPointKeyFrame Value="42,0" KeyTime="0:0:0.2"></EasingPointKeyFrame> 42 </PointAnimationUsingKeyFrames> 43 </Storyboard> 44 </BeginStoryboard> 45 </EventTrigger>View Code
item
按鈕的位置
不管是奇數個,還是偶數個,我們都想讓它以Y軸對稱
首先把圓分成8等份,每一份都是45度,也就是最多只能放下8個item,
從上圖可以看出來,其實就是奇數個線上上,偶數個在兩線之間
有個簡單的辦法,就是先在頂點依次順時針排列,每個item間隔45度,然後再逆時針旋轉,每多一個item就多轉45/2度(兩條分割線是45度,中間也就是45/2度),如下圖:
上圖是item終點的位置,起點的位置是在圓心.
動畫用DoubleAnimation控制item按鈕的位移,從圓心移動到計算後的位置
計算位置的代碼:
1 //函數是弧度制 2PI是360度 2 1 a = c * Math.Sin(2 * Math.PI / 8 * i - (itemsSource.Count - 1) * 2 * Math.PI / 8 / 2); 3 2 b = c * Math.Cos(2 * Math.PI / 8 * i - (itemsSource.Count - 1) * 2 * Math.PI / 8 / 2);
水球連接的部分
連接的部分是用兩個二次貝塞爾和一條直線做一個path
開始的時候,兩條貝塞爾曲線的高度是0,控制點在path所在矩形的邊上,然後對而塞爾曲線上面的點和控制點做動畫,分別向上和內移動,最終形成上圖右邊的圖形,然後把這個動畫和item按鈕向外部移動的動畫結合起來,就偽裝成了水球分離的效果.
上圖紅色矩形就是連接部分的path.動畫的過程就是Item按鈕的直徑和大圓相交的時候開始和item按鈕一起做動畫,最後移動到Item按鈕直徑所在的位置,整個距離就是Item的半徑+item到主體的距離+藍色的d,而藍色的d可以通過公式求出
開始的時候也是讓連接部分path在圓心的位置.定位方法和定位Item按鈕的方法是完全一樣的.這裡就不在重覆了.只說一下c邊的距離是:大圓和小圓圓心的距離-連接path高度的一半
源碼下載:WaterDropsButtonGroup.zip
參考連接:
- https://en.wikipedia.org/wiki/B%C3%A9zier_curve
- https://baike.baidu.com/item/%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF/1091769?fr=aladdin
- https://www.cnblogs.com/ArthurQQ/articles/1730214.html
- http://blog.csdn.net/chouglas/article/details/50967566