這一篇偏向於邏輯的比較多,放在這個系列里會不會欠妥呢?在中國交互性設計也是美工的份內職責哦~
所以沒有blend基礎的人也可以看懂這篇文章,不過要用到初中的幾何知識哦~親
相信很多人都在手機或者網頁上或者KTV的點歌系統里看到過旋轉木馬的目錄導航,這個是如何做的呢??
最終效果如下:(貌似有點太大了顯示不下,附加個閱覽地址:
http://space.silverlightchina.net/ltt147/TTcarousel/Default.html)
(特別鳴謝烤地瓜的答疑,和地瓜村眾人的熱心幫助)
1.總體思路
分析上面效果:一排方塊在轉圈,點擊的塊跑到最近的位置
圈:其實就是一個橢圓,只不過人的近大遠小的邏輯思維,大腦根據常識把它裝換成了一個空間。
最近的位置:其實就是橢圓的最下面,塊變得最大,所以感覺最近。
2.設計過程
首先我們來實現讓這堆塊圍繞成一個橢圓。
step.1 橢圓是這樣來產生滴~!↓
我以我微薄的幾何知識用blend畫出了上圖,右上角看到我們久違的橢圓公式。第一個公式當把y單獨提到等號一邊時發現是要開根號的這樣,就如圖所看到的,一個x對應着兩個y值還得去判斷正負號顯得麻煩了,就用第二個公式吧。其中a為x半軸,b為y半軸,d為角度。只需要一個角度d就可以確定出這個橢圓中的所有點了。
首先寫出變換塊位置的方法:
private void SetPosition(Grid item, double degree)
{
TransformGroup myTG = item.RenderTransform as TransformGroup;
TranslateTransform myTT = myTG.Children[0] as TranslateTransform;//位置變換
myTT.X = a * Math.Cos(degree);//計算x坐標
myTT.Y = b * Math.Sin(degree);//計算y坐標
item.Tag = degree;
ScaleTransform myST = myTG.Children[1] as ScaleTransform;//大小變換
myST.ScaleX = myST.ScaleY = (1 - scale) / b * myTT.Y + scale;
Canvas.SetZIndex(item, (int)(myTT.Y + 2 * b));//層次變換為Y軸的位置
}
step.2 跟隨鼠轉標起來吧
這樣傳入需要變換的塊和事先計算的該塊的位置所在就角度就可以確定該塊的位置、大小、層次了。單mousemove的時候,只要給每一個塊都重新定位,把鼠標的移動距離裝換為塊組需要旋轉的角度。
private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)//鼠標移動時轉動 { if (isPress)//如果當前為按住鼠標的狀態 { foreach (Grid item in LayoutRoot.Children)//遍歷所有的Grid { SetPosition(item, (double)item.Tag + (startPoint.X - e.GetPosition(this).X) * 0.005);//變換位置 } startPoint.X = e.GetPosition(this).X;//把但前位置賦給開始位置 } }
我把每個Grid的Tag用來存儲自己當前所在的角度。
step.3 點擊就轉到前面來啊,親~
接下來制作點擊塊轉動的動畫,即點擊塊后更具該塊所在的位置,計算出旋轉到90'時需要旋轉的角度,然后所有塊都轉這個角度(為什么是90°呢?應該是270°啊~因為wpf中的位移動畫,Y軸移動向下是為正,向上為負,這樣就剛好和我們課堂上通用的坐標系剛好上下翻轉了)
void newGrid_MouseUp(object sender, MouseButtonEventArgs e)//單擊其中一塊轉正 { double nowAngle = (double)((sender as Grid).Tag);//根據當前的塊的角度 double needAngle = Math.PI / 2 - nowAngle % (Math.PI * 2);//換算出把改塊轉正整體需要轉動的角度 foreach (Grid item in LayoutRoot.Children)//為每一個塊都添加動畫 { degree = (double)item.Tag;//獲取各個塊的角度 TransformGroup myTG = item.RenderTransform as TransformGroup; TranslateTransform myTT = myTG.Children[0] as TranslateTransform;//位置變換 Storyboard sb = new Storyboard();
sb.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); DoubleAnimation xAnimation = new DoubleAnimation(); xAnimation.To = a * Math.Cos(degree + needAngle);//計算x坐標 xAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); sb.Children.Add(xAnimation); Storyboard.SetTarget(xAnimation, myTT); Storyboard.SetTargetProperty(xAnimation, new PropertyPath("X")); DoubleAnimation yAnimation = new DoubleAnimation(); yAnimation.To = b * Math.Sin(degree + needAngle);//計算x坐標 yAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); sb.Children.Add(yAnimation); Storyboard.SetTarget(yAnimation, myTT); Storyboard.SetTargetProperty(yAnimation, new PropertyPath("Y")); ScaleTransform myST = myTG.Children[1] as ScaleTransform;//大小變換 DoubleAnimation xScaleAnimation = new DoubleAnimation(); DoubleAnimation yScaleAnimation = new DoubleAnimation(); xScaleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); yScaleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(Duration)); if (LayoutRoot.Children.IndexOf(item) == 0)//只添加一次動畫完成后遍歷控件改變位置 { yScaleAnimation.Completed += new EventHandler((s, events) =>//動畫結束后把整體的位置調整為動畫結束時的位置 { foreach (Grid item1 in LayoutRoot.Children) { SetPosition(item1, (double)item1.Tag); } }); } yScaleAnimation.To = xScaleAnimation.To = (1 - scale) / b * yAnimation.To + scale; sb.Children.Add(xScaleAnimation); Storyboard.SetTarget(xScaleAnimation, myST); Storyboard.SetTargetProperty(xScaleAnimation, new PropertyPath("ScaleX")); sb.Children.Add(yScaleAnimation); Storyboard.SetTarget(yScaleAnimation, myST); Storyboard.SetTargetProperty(yScaleAnimation, new PropertyPath("ScaleY")); sb.Begin();//開始動畫 item.Tag = degree + needAngle;//記錄最后的角度 } }
step.4 最靠近下面的要自動對正哦~
OK,接下來還差最有一點就是自動對正了,即當拖動時把最接近最近位置的塊自定定位到最近。
原理就是遍歷所有塊的但前位置,角度越接近90°的就模擬一下它的單擊
private void LayoutRoot_MouseUp(object sender, MouseButtonEventArgs e)//當鼠標彈起時判斷最近的塊,自動轉正 { double minNear = 100; Grid nearGrid = null; foreach (Grid item in LayoutRoot.Children)//找出最近的Grid { double near = Math.Abs(Math.PI / 2 - (double)item.Tag % (Math.PI * 2)); if (near < minNear) { minNear = near; nearGrid = item; } } newGrid_MouseUp(nearGrid, null);//模擬最近的塊被點了一次 isPress = false; }
做完收工。
下面是我修改左上角的參數實現的幾個比較好看的效果
效率也是很不錯的,上面的100個塊在我i7的電腦上一點都不卡哦。
大家如果調出什么好看的效果可以貼到回復里,交流下。
我把核心代碼都講了遍,如果還是有不懂的,可以給我留言。當然了這個silverlight版只是作為演示用得第一版,有很多細節方面我沒處理。至於源碼,因為我做的這個原版是WPF程序作為商用,所以經過多番考慮我還是不公布源碼了。當然了我是不會告訴你可以反編譯.xap來獲取源碼的。