本文經原作者授權以原創方式二次分享,歡迎轉載、分享。
原文作者:普通的地球人
原文地址:https://www.cnblogs.com/tsliwei/p/8041928.html
相關知識
這部分基本就是廢話,網上都能找到,我只不過是整理了以下.建議先不看,用到的時候可以回來看看
貝塞爾曲線
先來看兩組圖,有助于理解什么是貝塞爾曲線(圖片取自維基百科,參考鏈接)
二次貝塞爾曲線:


P0
是起點,P2
是終點,P1
是控制點
三次貝塞爾曲線:


P0
是起點,P3
是終點,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
段三次貝塞爾曲線組成的path
代替Ellipse
,因為Ellipse
是抖動不起來的,這樣就可以控制貝塞爾曲線的點來讓圓抖動.見參考鏈接
主體的大圓
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
,見代碼
<Path><Path.Data><PathGeometry><PathFigure?StartPoint="50,0"><BezierSegment?Point1="77.614237491541,0"?Point2="100,22.385762508459"?Point3="100,50"></BezierSegment><BezierSegment?Point1="100,77.614237491541"?Point2="77.614237491541,100"?Point3="50,100"></BezierSegment><BezierSegment?Point1="22.385762508459,100"?Point2="0,77.614237491541"?Point3="0,50"></BezierSegment><BezierSegment?Point1="0,22.385762508459"?Point2="22.385762508459,0"?Point3="50,0"></BezierSegment></PathFigure></PathGeometry></Path.Data>
</Path>
抖動動畫
由于圓是被12
個點控制的,讓圓抖動,也就是對12
個點做點動畫
可以用關鍵幀動畫,這樣控制的比較細致,要注意的是,銜接的地方要平滑.我這里做的比較簡陋,就找了一個變換后的圖形,重復了5次
.如果你有興趣,可以多做些,做的越多,動畫看起來表現力越強
這里我并沒有去研究什么算法,就是簡單的在blend
里,找了一些點
見代碼:
<EventTrigger?RoutedEvent="Click"><BeginStoryboard><Storyboard><PointAnimationUsingKeyFrames?Storyboard.TargetName="pf_main"?Storyboard.TargetProperty="StartPoint"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="40,0"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main0"?Storyboard.TargetProperty="Point1"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="68,-10"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main0"?Storyboard.TargetProperty="Point2"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="115,14"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main0"?Storyboard.TargetProperty="Point3"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="100,66"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main1"?Storyboard.TargetProperty="Point1"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="100,67"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main1"?Storyboard.TargetProperty="Point2"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="85,111"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main1"?Storyboard.TargetProperty="Point3"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="33,103"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main2"?Storyboard.TargetProperty="Point1"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="22,103"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main2"?Storyboard.TargetProperty="Point2"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="-15,85"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main2"?Storyboard.TargetProperty="Point3"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="-6,50"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main3"?Storyboard.TargetProperty="Point1"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="4,9"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main3"?Storyboard.TargetProperty="Point2"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="41,-1"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames><PointAnimationUsingKeyFrames?Storyboard.TargetName="bs_main3"?Storyboard.TargetProperty="Point3"?BeginTime="0:0:0.7"?AutoReverse="True"?RepeatBehavior="5x"?FillBehavior="Stop"><EasingPointKeyFrame?Value="42,0"?KeyTime="0:0:0.2"></EasingPointKeyFrame></PointAnimationUsingKeyFrames></Storyboard></BeginStoryboard>
</EventTrigger>
item
按鈕的位置 不管是奇數個,還是偶數個,我們都想讓它以Y軸對稱
首先把圓分成8
等份,每一份都是45
度,也就是最多只能放下8
個item
,
從上圖可以看出來,其實就是奇數個在線上,偶數個在兩線之間
有個簡單的辦法,就是先在頂點依次順時針排列,每個item
間隔45
度,然后再逆時針旋轉,每多一個item
就多轉45/2
度(兩條分割線是45
度,中間也就是45/2
度),如下圖:
上圖是item終點的位置,起點的位置是在圓心.
動畫用DoubleAnimation
控制item
按鈕的位移,從圓心移動到計算后的位置
計算位置的代碼:
//函數是弧度制?2PI是360度
a?=?c?*?Math.Sin(2?*?Math.PI?/?8?*?i?-?(itemsSource.Count?-?1)?*?2?*?Math.PI?/?8?/?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
高度的一半