当前位置: 代码迷 >> .NET相关 >> WinPhone学习笔记(4)——磁贴
  详细解决方案

WinPhone学习笔记(4)——磁贴

热度:316   发布时间:2016-04-24 02:57:10.0
WinPhone学习笔记(四)——磁贴

对每个Windows Phone的使用者来说,给他们的第一印象就是大大小小的磁贴——Metro,本篇介绍的是Windows Phone的磁贴,提到的有开始菜单的磁贴,也有在App里面的磁贴。

开始菜单的磁贴

首先介绍了一下每个磁贴的构造,每个磁贴分正反两面,正反两面都有图标,而正面有一个标题和统计数量(一般用作消息推送的时候用),在背面就有一个描述性的内容,下图就摘自MSDN上的图片,图中黑色字体其实就是每个磁贴数据类的属性,这个稍后会提到

对于一个磁贴来说,他的图片像素建议是173*173像素的,占用空间控制在80KB以内,它各个部分更详尽的数据大小如下图。

在开始菜单中的磁贴分两类,一类是App本身启动用的,通过在应用程序列表中创建的磁贴,叫应用程序磁贴;另一类是由App创建的,那个叫次要磁贴。

在控制开始菜单上的磁贴,主要有两个类,一个是StandandTileData,另一个是ShellTile。前者则是之前提过有一个类存储磁贴上的数据信息那个类,后者是负责管理开始菜单中的磁贴(包括了增删改查),但只局限于本App的磁贴。

下面的代码则磁贴创建的代码

 1 StandardTileData tileData = new StandardTileData() 2  3 { 4  5 Title = "Test Tile", 6  7 BackContent = "The " + ShellTile.ActiveTiles.Count() + " One", 8  9 BackTitle = ShellTile.ActiveTiles.Count().ToString(),10 11 Count = ShellTile.ActiveTiles.Count()12 13 };14 15 ShellTile.Create(new Uri(NavigationService.Source.ToString()+"?key="+Guid.NewGuid().ToString(), UriKind.Relative), tileData);

添加磁贴就是ShellTile的静态方法Create,传入的是点击磁贴后要跳转到的页面的URI还有这个磁贴的数据类,对于上面的代码,如果创建了一次磁贴之后再次执行则会抛出异常,原因在于对一个App的次要磁贴来说,它们的URI不允许重复,那遇到创建多个磁贴都是跳转到相同的页面时,可以给URI上面加上不同是QueryString来使得各个URI不一样。

ShellTile的ActiveTiles静态属性是获取这个App所有开始菜单上磁贴的枚举,是一个ShellTile的泛型集合。要获取这个App中的某一个磁贴只能遍历这个集合。有个特别之处就是不管这个App有没有放应用程序磁贴到开始菜单中,第一个元素绝对是应用程序磁贴,次要磁贴则是从第二个元素开始。

更新磁贴只是从ActiveTiles获取了相应的磁贴类之后,然后用一个StandandTileData赋上新的值,通过该磁贴的ShellTile实例的Update方法把StandandTileData传过去就可以了。

删除磁贴也是通过ActiveTiles获取了相应的磁贴类ShellTile实例,再调用它的Delete方法,但注意的一点是这里的删除只能删除次要磁贴,应用程序磁贴是不允许删除的。

应用程序内部的磁贴

类似开始菜单中的磁贴也可以添加到App内部。但它就不是ShellTile了,是HubTile,这个控件并非单纯从工具箱可以拖到页面中去,这个需要引用Toolkit,在以前WP7时使用Toolkit相对简单,但是WP8的话则需要联机获取dll了。

在vs中打开"扩展与更新"窗口,搜索"Nuget";

搜索出来了"Nuget Package Manager"便安装,安装完毕后就记得重启VS;在"扩展与更新"窗口中重启"Nuget Package Manager"。

现在就可以在引用文件夹中添加dll了。选的是"管理NuGet程序包"。

搜索"windows phone toolkit"进行安装,

最后在包管理器控制台中输入命令"Install-Package WPToolkit"就可以完成dll的添加了。包管理控制台打开方式如下图。

在需要使用该dll的xaml页面肯要添加对应的xml命名空间

Xmlns:toolkit="clr-namespace;Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" 

在xaml中添加下面语句则可以往页面成功添加一个磁贴

<toolkit:HubTile Grid.Row="1" Grid.Column="1" Background="Red" Source="Assets/Tiles/FlipCycleTileSmall.png" Title="Metro" Message="This is Metro in App"/>

一个HubTile一共有下面五种状态,这个这个磁贴用到的属性其实在上面一条语句中都可以看出来,Background是磁贴的背景色;Source是磁贴中图片,这个图片就只有一面才有,反面就没有了,Title则是那个很醒目的磁贴的标题,在磁贴的背面也有;Message是在磁贴背面

 

运行的时候会发现磁贴是贴在了页面上了,但是手点击上去就没有了开始菜单中的那种倾斜效果,这个磁贴的倾斜效果是这个Toolkit的另外一个附加属性 TiltEffect.IsEnable。它是一个布尔类型,True表示使用倾斜效果。还需要在隐藏文件的构造函数中加入这个控件的类型

<toolkit:HubTile toolkit:TiltEffect.IsTiltEnabled="True" Grid.Row="1" Grid.Column="1" Background="Red" Source="Assets/Tiles/FlipCycleTileSmall.png" Title="Metro" Message="This is Metro in App"/>

 

public TileTestPage(){InitializeComponent();ControlTiltEffect.TiltEffect.TiltableItems.Add(typeof(HubTile));}

 

但是用Toolkit的效果不是很明显,而且有限制,有一些控件虽然用上了但也没有倾斜的效果。在网上查看资料时发现有个老外也写了一个倾斜效果,效果比微软提供的要明显,而且还可以调节倾斜的角度。两个类的代码如下

  1 using System;  2 using System.Windows;  3 using System.Windows.Controls;  4 using System.Windows.Input;  5 using System.Windows.Media;  6 using System.Windows.Media.Animation;  7 using System.Collections.Generic;  8 using System.Windows.Controls.Primitives;  9  10  11 #if WINDOWS_PHONE 12 using Microsoft.Phone.Controls; 13 #endif 14  15 namespace ControlTiltEffect 16 { 17     /// <summary> 18     /// This code provides attached properties for adding a 'tilt' effect to all controls within a container. 19     /// </summary> 20     public class TiltEffect : DependencyObject 21     { 22  23         #region Constructor and Static Constructor 24         /// <summary> 25         /// This is not a constructable class, but it cannot be static because it derives from DependencyObject. 26         /// </summary> 27         private TiltEffect() 28         { 29         } 30  31         /// <summary> 32         /// Initialize the static properties 33         /// </summary> 34         static TiltEffect() 35         { 36             // The tiltable items list. 37             TiltableItems = new List<Type>() { typeof(ButtonBase), typeof(ListBoxItem) }; 38             UseLogarithmicEase = false; 39         } 40  41         #endregion 42  43  44         #region Fields and simple properties 45  46         // These constants are the same as the built-in effects 47         /// <summary> 48         /// Maximum amount of tilt, in radians 49         /// </summary> 50         const double MaxAngle = 0.3; 51  52         /// <summary> 53         /// Maximum amount of depression, in pixels 54         /// </summary> 55         const double MaxDepression = 25; 56  57         /// <summary> 58         /// Delay between releasing an element and the tilt release animation playing 59         /// </summary> 60         static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200); 61  62         /// <summary> 63         /// Duration of tilt release animation 64         /// </summary> 65         static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100); 66  67         /// <summary> 68         /// The control that is currently being tilted 69         /// </summary> 70         static FrameworkElement currentTiltElement; 71  72         /// <summary> 73         /// The single instance of a storyboard used for all tilts 74         /// </summary> 75         static Storyboard tiltReturnStoryboard; 76  77         /// <summary> 78         /// The single instance of an X rotation used for all tilts 79         /// </summary> 80         static DoubleAnimation tiltReturnXAnimation; 81  82         /// <summary> 83         /// The single instance of a Y rotation used for all tilts 84         /// </summary> 85         static DoubleAnimation tiltReturnYAnimation; 86  87         /// <summary> 88         /// The single instance of a Z depression used for all tilts 89         /// </summary> 90         static DoubleAnimation tiltReturnZAnimation; 91  92         /// <summary> 93         /// The center of the tilt element 94         /// </summary> 95         static Point currentTiltElementCenter; 96  97         /// <summary> 98         /// Whether the animation just completed was for a 'pause' or not 99         /// </summary>100         static bool wasPauseAnimation = false;101 102         /// <summary>103         /// Whether to use a slightly more accurate (but slightly slower) tilt animation easing function104         /// </summary>105         public static bool UseLogarithmicEase { get; set; }106 107         /// <summary>108         /// Default list of items that are tiltable109         /// </summary>110         public static List<Type> TiltableItems { get; private set; }111 112         #endregion113 114 115         #region Dependency properties116 117         /// <summary>118         /// Whether the tilt effect is enabled on a container (and all its children)119         /// </summary>120         public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(121           "IsTiltEnabled",122           typeof(bool),123           typeof(TiltEffect),124           new PropertyMetadata(OnIsTiltEnabledChanged)125           );126 127         /// <summary>128         /// Gets the IsTiltEnabled dependency property from an object129         /// </summary>130         /// <param name="source">The object to get the property from</param>131         /// <returns>The property's value</returns>132         public static bool GetIsTiltEnabled(DependencyObject source) { return (bool)source.GetValue(IsTiltEnabledProperty); }133 134         /// <summary>135         /// Sets the IsTiltEnabled dependency property on an object136         /// </summary>137         /// <param name="source">The object to set the property on</param>138         /// <param name="value">The value to set</param>139         public static void SetIsTiltEnabled(DependencyObject source, bool value) { source.SetValue(IsTiltEnabledProperty, value); }140 141         /// <summary>142         /// Suppresses the tilt effect on a single control that would otherwise be tilted143         /// </summary>144         public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(145           "SuppressTilt",146           typeof(bool),147           typeof(TiltEffect),148           null149           );150 151         /// <summary>152         /// Gets the SuppressTilt dependency property from an object153         /// </summary>154         /// <param name="source">The object to get the property from</param>155         /// <returns>The property's value</returns>156         public static bool GetSuppressTilt(DependencyObject source) { return (bool)source.GetValue(SuppressTiltProperty); }157 158         /// <summary>159         /// Sets the SuppressTilt dependency property from an object160         /// </summary>161         /// <param name="source">The object to get the property from</param>162         /// <returns>The property's value</returns>163         public static void SetSuppressTilt(DependencyObject source, bool value) { source.SetValue(SuppressTiltProperty, value); }164 165 166         /// <summary>167         /// Property change handler for the IsTiltEnabled dependency property168         /// </summary>169         /// <param name="target">The element that the property is atteched to</param>170         /// <param name="args">Event args</param>171         /// <remarks>172         /// Adds or removes event handlers from the element that has been (un)registered for tilting173         /// </remarks>174         static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)175         {176             if (target is FrameworkElement)177             {178                 // Add / remove the event handler if necessary179                 if ((bool)args.NewValue == true)180                 {181                     (target as FrameworkElement).ManipulationStarted += TiltEffect_ManipulationStarted;182                 }183                 else184                 {185                     (target as FrameworkElement).ManipulationStarted -= TiltEffect_ManipulationStarted;186                 }187             }188         }189 190         #endregion191 192 193         #region Top-level manipulation event handlers194 195         /// <summary>196         /// Event handler for ManipulationStarted197         /// </summary>198         /// <param name="sender">sender of the event - this will be the tilt container (eg, entire page)</param>199         /// <param name="e">event args</param>200         static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e)201         {202 203             TryStartTiltEffect(sender as FrameworkElement, e);204         }205 206         /// <summary>207         /// Event handler for ManipulationDelta208         /// </summary>209         /// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param>210         /// <param name="e">event args</param>211         static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)212         {213 214             ContinueTiltEffect(sender as FrameworkElement, e);215         }216 217         /// <summary>218         /// Event handler for ManipulationCompleted219         /// </summary>220         /// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param>221         /// <param name="e">event args</param>222         static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)223         {224 225             EndTiltEffect(currentTiltElement);226         }227 228         #endregion229 230 231         #region Core tilt logic232 233         /// <summary>234         /// Checks if the manipulation should cause a tilt, and if so starts the tilt effect235         /// </summary>236         /// <param name="source">The source of the manipulation (the tilt container, eg entire page)</param>237         /// <param name="e">The args from the ManipulationStarted event</param>238         static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e)239         {240             foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors())241             {242                 foreach (Type t in TiltableItems)243                 {244                     if (t.IsAssignableFrom(ancestor.GetType()))245                     {246                         if ((bool)ancestor.GetValue(SuppressTiltProperty) != true)247                         {248                             // Use first child of the control, so that you can add transforms and not249                             // impact any transforms on the control itself250                             FrameworkElement element = VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement;251                             FrameworkElement container = e.ManipulationContainer as FrameworkElement;252 253                             if (element == null || container == null)254                                 return;255 256                             // Touch point relative to the element being tilted257                             Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);258 259                             // Center of the element being tilted260                             Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);261 262                             // Camera adjustment263                             Point centerToCenterDelta = GetCenterToCenterDelta(element, source);264 265                             BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);266                             return;267                         }268                     }269                 }270             }271         }272 273         /// <summary>274         /// Computes the delta between the centre of an element and its container275         /// </summary>276         /// <param name="element">The element to compare</param>277         /// <param name="container">The element to compare against</param>278         /// <returns>A point that represents the delta between the two centers</returns>279         static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)280         {281             Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);282             Point containerCenter;283 284 #if WINDOWS_PHONE285 286             // Need to special-case the frame to handle different orientations287             if (container is PhoneApplicationFrame)288             {289                 PhoneApplicationFrame frame = container as PhoneApplicationFrame;290 291                 // Switch width and height in landscape mode292                 if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)293                 {294 295                     containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);296                 }297                 else298                     containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);299             }300             else301                 containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);302 #else303 304             containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);305 306 #endif307 308             Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter);309             Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);310 311             return result;312         }313 314         /// <summary>315         /// Begins the tilt effect by preparing the control and doing the initial animation316         /// </summary>317         /// <param name="element">The element to tilt </param>318         /// <param name="touchPoint">The touch point, in element coordinates</param>319         /// <param name="centerPoint">The center point of the element in element coordinates</param>320         /// <param name="centerDelta">The delta between the <paramref name="element"/>'s center and 321         /// the container's center</param>322         static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)323         {324 325 326             if (tiltReturnStoryboard != null)327                 StopTiltReturnStoryboardAndCleanup();328 329             if (PrepareControlForTilt(element, centerDelta) == false)330                 return;331 332             currentTiltElement = element;333             currentTiltElementCenter = centerPoint;334             PrepareTiltReturnStoryboard(element);335 336             ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);337         }338 339         /// <summary>340         /// Prepares a control to be tilted by setting up a plane projection and some event handlers341         /// </summary>342         /// <param name="element">The control that is to be tilted</param>343         /// <param name="centerDelta">Delta between the element's center and the tilt container's</param>344         /// <returns>true if successful; false otherwise</returns>345         /// <remarks>346         /// This method is conservative; it will fail any attempt to tilt a control that already347         /// has a projection on it348         /// </remarks>349         static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)350         {351             // Prevents interference with any existing transforms352             if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))353                 return false;354 355             TranslateTransform transform = new TranslateTransform();356             transform.X = centerDelta.X;357             transform.Y = centerDelta.Y;358             element.RenderTransform = transform;359 360             PlaneProjection projection = new PlaneProjection();361             projection.GlobalOffsetX = -1 * centerDelta.X;362             projection.GlobalOffsetY = -1 * centerDelta.Y;363             element.Projection = projection;364 365             element.ManipulationDelta += TiltEffect_ManipulationDelta;366             element.ManipulationCompleted += TiltEffect_ManipulationCompleted;367 368             return true;369         }370 371         /// <summary>372         /// Removes modifications made by PrepareControlForTilt373         /// </summary>374         /// <param name="element">THe control to be un-prepared</param>375         /// <remarks>376         /// This method is basic; it does not do anything to detect if the control being un-prepared377         /// was previously prepared378         /// </remarks>379         static void RevertPrepareControlForTilt(FrameworkElement element)380         {381             element.ManipulationDelta -= TiltEffect_ManipulationDelta;382             element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;383             element.Projection = null;384             element.RenderTransform = null;385         }386 387         /// <summary>388         /// Creates the tilt return storyboard (if not already created) and targets it to the projection389         /// </summary>390         /// <param name="projection">the projection that should be the target of the animation</param>391         static void PrepareTiltReturnStoryboard(FrameworkElement element)392         {393 394             if (tiltReturnStoryboard == null)395             {396                 tiltReturnStoryboard = new Storyboard();397                 tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;398 399                 tiltReturnXAnimation = new DoubleAnimation();400                 Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty));401                 tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;402                 tiltReturnXAnimation.To = 0;403                 tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;404 405                 tiltReturnYAnimation = new DoubleAnimation();406                 Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty));407                 tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;408                 tiltReturnYAnimation.To = 0;409                 tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;410 411                 tiltReturnZAnimation = new DoubleAnimation();412                 Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));413                 tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;414                 tiltReturnZAnimation.To = 0;415                 tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;416 417                 if (UseLogarithmicEase)418                 {419                     tiltReturnXAnimation.EasingFunction = new LogarithmicEase();420                     tiltReturnYAnimation.EasingFunction = new LogarithmicEase();421                     tiltReturnZAnimation.EasingFunction = new LogarithmicEase();422                 }423 424                 tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);425                 tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);426                 tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);427             }428 429             Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);430             Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);431             Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);432         }433 434 435         /// <summary>436         /// Continues a tilt effect that is currently applied to an element, presumably because437         /// the user moved their finger438         /// </summary>439         /// <param name="element">The element being tilted</param>440         /// <param name="e">The manipulation event args</param>441         static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e)442         {443             FrameworkElement container = e.ManipulationContainer as FrameworkElement;444             if (container == null || element == null)445                 return;446 447             Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);448 449             // If touch moved outside bounds of element, then pause the tilt (but don't cancel it)450             if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)451             {452 453                 PauseTiltEffect();454                 return;455             }456 457             // Apply the updated tilt effect458             ApplyTiltEffect(currentTiltElement, e.ManipulationOrigin, currentTiltElementCenter);459         }460 461         /// <summary>462         /// Ends the tilt effect by playing the animation  463         /// </summary>464         /// <param name="element">The element being tilted</param>465         static void EndTiltEffect(FrameworkElement element)466         {467             if (element != null)468             {469                 element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;470                 element.ManipulationDelta -= TiltEffect_ManipulationDelta;471             }472 473             if (tiltReturnStoryboard != null)474             {475                 wasPauseAnimation = false;476                 if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)477                     tiltReturnStoryboard.Begin();478             }479             else480                 StopTiltReturnStoryboardAndCleanup();481         }482 483         /// <summary>484         /// Handler for the storyboard complete event485         /// </summary>486         /// <param name="sender">sender of the event</param>487         /// <param name="e">event args</param>488         static void TiltReturnStoryboard_Completed(object sender, EventArgs e)489         {490             if (wasPauseAnimation)491                 ResetTiltEffect(currentTiltElement);492             else493                 StopTiltReturnStoryboardAndCleanup();494         }495 496         /// <summary>497         /// Resets the tilt effect on the control, making it appear 'normal' again 498         /// </summary>499         /// <param name="element">The element to reset the tilt on</param>500         /// <remarks>501         /// This method doesn't turn off the tilt effect or cancel any current502         /// manipulation; it just temporarily cancels the effect503         /// </remarks>504         static void ResetTiltEffect(FrameworkElement element)505         {506             PlaneProjection projection = element.Projection as PlaneProjection;507             projection.RotationY = 0;508             projection.RotationX = 0;509             projection.GlobalOffsetZ = 0;510         }511 512         /// <summary>513         /// Stops the tilt effect and release resources applied to the currently-tilted control514         /// </summary>515         static void StopTiltReturnStoryboardAndCleanup()516         {517             if (tiltReturnStoryboard != null)518                 tiltReturnStoryboard.Stop();519 520             RevertPrepareControlForTilt(currentTiltElement);521         }522 523         /// <summary>524         /// Pauses the tilt effect so that the control returns to the 'at rest' position, but doesn't525         /// stop the tilt effect (handlers are still attached, etc.)526         /// </summary>527         static void PauseTiltEffect()528         {529             if ((tiltReturnStoryboard != null) && !wasPauseAnimation)530             {531                 tiltReturnStoryboard.Stop();532                 wasPauseAnimation = true;533                 tiltReturnStoryboard.Begin();534             }535         }536 537         /// <summary>538         /// Resets the storyboard to not running539         /// </summary>540         private static void ResetTiltReturnStoryboard()541         {542             tiltReturnStoryboard.Stop();543             wasPauseAnimation = false;544         }545 546         /// <summary>547         /// Applies the tilt effect to the control548         /// </summary>549         /// <param name="element">the control to tilt</param>550         /// <param name="touchPoint">The touch point, in the container's coordinates</param>551         /// <param name="centerPoint">The center point of the container</param>552         static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)553         {554             // Stop any active animation555             ResetTiltReturnStoryboard();556 557             // Get relative point of the touch in percentage of container size558             Point normalizedPoint = new Point(559                 Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),560                 Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));561 562             // Shell values563             double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);564             double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);565             double xDirection = -Math.Sign(normalizedPoint.X - 0.5);566             double yDirection = Math.Sign(normalizedPoint.Y - 0.5);567             double angleMagnitude = xMagnitude + yMagnitude;568             double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;569 570             double angle = angleMagnitude * MaxAngle * 180 / Math.PI;571             double depression = (1 - angleMagnitude) * MaxDepression;572 573             // RotationX and RotationY are the angles of rotations about the x- or y-*axis*;574             // to achieve a rotation in the x- or y-*direction*, we need to swap the two.575             // That is, a rotation to the left about the y-axis is a rotation to the left in the x-direction,576             // and a rotation up about the x-axis is a rotation up in the y-direction.577             PlaneProjection projection = element.Projection as PlaneProjection;578             projection.RotationY = angle * xAngleContribution * xDirection;579             projection.RotationX = angle * (1 - xAngleContribution) * yDirection;580             projection.GlobalOffsetZ = -depression;581         }582 583         #endregion584 585 586         #region Custom easing function587 588         /// <summary>589         /// Provides an easing function for the tilt return590         /// </summary>591         private class LogarithmicEase : EasingFunctionBase592         {593             /// <summary>594             /// Computes the easing function595             /// </summary>596             /// <param name="normalizedTime">The time</param>597             /// <returns>The eased value</returns>598             protected override double EaseInCore(double normalizedTime)599             {600                 return Math.Log(normalizedTime + 1) / 0.693147181; // ln(t + 1) / ln(2)601             }602         }603 604         #endregion605     }606 607     /// <summary>608     /// Couple of simple helpers for walking the visual tree609     /// </summary>610     static class TreeHelpers611     {612         /// <summary>613         /// Gets the ancestors of the element, up to the root614         /// </summary>615         /// <param name="node">The element to start from</param>616         /// <returns>An enumerator of the ancestors</returns>617         public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)618         {619             FrameworkElement parent = node.GetVisualParent();620             while (parent != null)621             {622                 yield return parent;623                 parent = parent.GetVisualParent();624             }625         }626 627         /// <summary>628         /// Gets the visual parent of the element629         /// </summary>630         /// <param name="node">The element to check</param>631         /// <returns>The visual parent</returns>632         public static FrameworkElement GetVisualParent(this FrameworkElement node)633         {634             return VisualTreeHelper.GetParent(node) as FrameworkElement;635         }636     }637 }
TiltEffect.cs
  1     public static class MetroInMotion  2     {  3         #region AnimationLevel  4   5         public static int GetAnimationLevel(DependencyObject obj)  6         {  7             return (int)obj.GetValue(AnimationLevelProperty);  8         }  9  10         public static void SetAnimationLevel(DependencyObject obj, int value) 11         { 12             obj.SetValue(AnimationLevelProperty, value); 13         } 14  15  16         public static readonly DependencyProperty AnimationLevelProperty = 17             DependencyProperty.RegisterAttached("AnimationLevel", typeof(int), 18             typeof(MetroInMotion), new PropertyMetadata(-1)); 19  20         #endregion 21  22         #region Tilt 23  24         public static double GetTilt(DependencyObject obj) 25         { 26             return (double)obj.GetValue(TiltProperty); 27         } 28  29         public static void SetTilt(DependencyObject obj, double value) 30         { 31             obj.SetValue(TiltProperty, value); 32         } 33  34  35         public static readonly DependencyProperty TiltProperty = 36             DependencyProperty.RegisterAttached("Tilt", typeof(double), 37             typeof(MetroInMotion), new PropertyMetadata(2.0, OnTiltChanged)); 38  39         /// <summary> 40         /// The extent of the tilt action, the larger the number, the bigger the tilt 41         /// </summary> 42         private static double TiltAngleFactor = 4; 43  44         /// <summary> 45         /// The extent of the scaling action, the smaller the number, the greater the scaling. 46         /// </summary> 47         private static double ScaleFactor = 100; 48  49         private static void OnTiltChanged(DependencyObject d, 50           DependencyPropertyChangedEventArgs args) 51         { 52             FrameworkElement targetElement = d as FrameworkElement; 53  54             double tiltFactor = GetTilt(d); 55  56             // create the required transformations 57             var projection = new PlaneProjection(); 58             var scale = new ScaleTransform(); 59             var translate = new TranslateTransform(); 60  61             var transGroup = new TransformGroup(); 62             transGroup.Children.Add(scale); 63             transGroup.Children.Add(translate); 64  65             // associate with the target element 66             targetElement.Projection = projection; 67             targetElement.RenderTransform = transGroup; 68             targetElement.RenderTransformOrigin = new Point(0.5, 0.5); 69  70             targetElement.MouseLeftButtonDown += (s, e) => 71             { 72                 var clickPosition = e.GetPosition(targetElement); 73  74                 // find the maximum of width / height 75                 double maxDimension = Math.Max(targetElement.ActualWidth, targetElement.ActualHeight); 76  77                 // compute the normalised horizontal distance from the centre 78                 double distanceFromCenterX = targetElement.ActualWidth / 2 - clickPosition.X; 79                 double normalisedDistanceX = 2 * distanceFromCenterX / maxDimension; 80  81                 // rotate around the Y axis  82                 projection.RotationY = normalisedDistanceX * TiltAngleFactor * tiltFactor; 83  84                 // compute the normalised vertical distance from the centre 85                 double distanceFromCenterY = targetElement.ActualHeight / 2 - clickPosition.Y; 86                 double normalisedDistanceY = 2 * distanceFromCenterY / maxDimension; 87  88                 // rotate around the X axis,  89                 projection.RotationX = -normalisedDistanceY * TiltAngleFactor * tiltFactor; 90  91                 // find the distance to centre 92                 double distanceToCentre = Math.Sqrt(normalisedDistanceX * normalisedDistanceX 93                   + normalisedDistanceY * normalisedDistanceY); 94  95                 // scale accordingly 96                 double scaleVal = tiltFactor * (1 - distanceToCentre) / ScaleFactor; 97                 scale.ScaleX = 1 - scaleVal; 98                 scale.ScaleY = 1 - scaleVal; 99 100                 // offset the plane transform101                 var rootElement = Application.Current.RootVisual as FrameworkElement;102                 var relativeToCentre = (targetElement.GetRelativePosition(rootElement).Y - rootElement.ActualHeight / 2) / 2;103                 translate.Y = -relativeToCentre;104                 projection.LocalOffsetY = +relativeToCentre;105 106             };107 108             targetElement.ManipulationCompleted += (s, e) =>109             {110                 var sb = new Storyboard();111                 sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationY", projection));112                 sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationX", projection));113                 sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleX", scale));114                 sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleY", scale));115                 sb.Begin();116 117                 translate.Y = 0;118                 projection.LocalOffsetY = 0;119             };120 121         }122 123 124         #endregion125 126         #region IsPivotAnimated127 128         public static bool GetIsPivotAnimated(DependencyObject obj)129         {130             return (bool)obj.GetValue(IsPivotAnimatedProperty);131         }132 133         public static void SetIsPivotAnimated(DependencyObject obj, bool value)134         {135             obj.SetValue(IsPivotAnimatedProperty, value);136         }137 138         public static readonly DependencyProperty IsPivotAnimatedProperty =139             DependencyProperty.RegisterAttached("IsPivotAnimated", typeof(bool),140             typeof(MetroInMotion), new PropertyMetadata(false, OnIsPivotAnimatedChanged));141 142         private static void OnIsPivotAnimatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)143         {144             ItemsControl list = d as ItemsControl;145 146             list.Loaded += (s2, e2) =>147             {148                 // locate the pivot control that this list is within149                 Pivot pivot = list.Ancestors<Pivot>().Single() as Pivot;150 151                 // and its index within the pivot152                 int pivotIndex = pivot.Items.IndexOf(list.Ancestors<PivotItem>().Single());153 154                 bool selectionChanged = false;155 156                 pivot.SelectionChanged += (s3, e3) =>157                 {158                     selectionChanged = true;159                 };160 161                 // handle manipulation events which occur when the user162                 // moves between pivot items163                 pivot.ManipulationCompleted += (s, e) =>164                 {165                     if (!selectionChanged)166                         return;167 168                     selectionChanged = false;169 170                     if (pivotIndex != pivot.SelectedIndex)171                         return;172 173                     // determine which direction this tab will be scrolling in from174                     bool fromRight = e.TotalManipulation.Translation.X <= 0;175 176 177                     // iterate over each of the items in view178                     var items = list.GetItemsInView().ToList();179                     for (int index = 0; index < items.Count; index++)180                     {181                         var lbi = items[index];182 183                         list.Dispatcher.BeginInvoke(() =>184                         {185                             var animationTargets = lbi.Descendants()186                                                    .Where(p => MetroInMotion.GetAnimationLevel(p) > -1);187                             foreach (FrameworkElement target in animationTargets)188                             {189                                 // trigger the required animation190                                 GetSlideAnimation(target, fromRight).Begin();191                             }192                         });193                     };194 195                 };196             };197         }198 199 200         #endregion201 202         /// <summary>203         /// Animates each element in order, creating a 'peel' effect. The supplied action204         /// is invoked when the animation ends.205         /// </summary>206         public static void Peel(this IEnumerable<FrameworkElement> elements, Action endAction)207         {208             var elementList = elements.ToList();209             var lastElement = elementList.Last();210 211             // iterate over all the elements, animating each of them212             double delay = 0;213             foreach (FrameworkElement element in elementList)214             {215                 var sb = GetPeelAnimation(element, delay);216 217                 // add a Completed event handler to the last element218                 if (element.Equals(lastElement))219                 {220                     sb.Completed += (s, e) =>221                     {222                         endAction();223                     };224                 }225 226                 sb.Begin();227                 delay += 50;228             }229         }230 231 232         /// <summary>233         /// Enumerates all the items that are currently visible in am ItemsControl. This implementation assumes234         /// that a VirtualizingStackPanel is being used as the ItemsPanel.235         /// </summary>236         public static IEnumerable<FrameworkElement> GetItemsInView(this ItemsControl itemsControl)237         {238             // locate the stack panel that hosts the items239             VirtualizingStackPanel vsp = itemsControl.Descendants<VirtualizingStackPanel>().First() as VirtualizingStackPanel;240 241             // iterate over each of the items in view242             int firstVisibleItem = (int)vsp.VerticalOffset;243             int visibleItemCount = (int)vsp.ViewportHeight;244             for (int index = firstVisibleItem; index <= firstVisibleItem + visibleItemCount + 1; index++)245             {246                 var item = itemsControl.ItemContainerGenerator.ContainerFromIndex(index) as FrameworkElement;247                 if (item == null)248                     continue;249 250                 yield return item;251             }252         }253 254         /// <summary>255         /// Creates a PlaneProjection and associates it with the given element, returning256         /// a Storyboard which will animate the PlaneProjection to 'peel' the item257         /// from the screen.258         /// </summary>259         private static Storyboard GetPeelAnimation(FrameworkElement element, double delay)260         {261             Storyboard sb;262 263             var projection = new PlaneProjection()264             {265                 CenterOfRotationX = -0.1266             };267             element.Projection = projection;268 269             // compute the angle of rotation required to make this element appear270             // at a 90 degree angle at the edge of the screen.271             var width = element.ActualWidth;272             var targetAngle = Math.Atan(1000 / (width / 2));273             targetAngle = targetAngle * 180 / Math.PI;274 275             // animate the projection276             sb = new Storyboard();277             sb.BeginTime = TimeSpan.FromMilliseconds(delay);278             sb.Children.Add(CreateAnimation(0, -(180 - targetAngle), 0.3, "RotationY", projection));279             sb.Children.Add(CreateAnimation(0, 23, 0.3, "RotationZ", projection));280             sb.Children.Add(CreateAnimation(0, -23, 0.3, "GlobalOffsetZ", projection));281             return sb;282         }283 284         private static DoubleAnimation CreateAnimation(double? from, double? to, double duration,285           string targetProperty, DependencyObject target)286         {287             var db = new DoubleAnimation();288             db.To = to;289             db.From = from;290             db.EasingFunction = new SineEase();291             db.Duration = TimeSpan.FromSeconds(duration);292             Storyboard.SetTarget(db, target);293             Storyboard.SetTargetProperty(db, new PropertyPath(targetProperty));294             return db;295         }296 297         /// <summary>298         /// Creates a TranslateTransform and associates it with the given element, returning299         /// a Storyboard which will animate the TranslateTransform with a SineEase function300         /// </summary>301         private static Storyboard GetSlideAnimation(FrameworkElement element, bool fromRight)302         {303             double from = fromRight ? 80 : -80;304 305             Storyboard sb;306             double delay = (MetroInMotion.GetAnimationLevel(element)) * 0.1 + 0.1;307 308             TranslateTransform trans = new TranslateTransform() { X = from };309             element.RenderTransform = trans;310 311             sb = new Storyboard();312             sb.BeginTime = TimeSpan.FromSeconds(delay);313             sb.Children.Add(CreateAnimation(from, 0, 0.8, "X", trans));314             return sb;315         }316 317     }318 319     public static class ExtensionMethods320     {321         public static Point GetRelativePosition(this UIElement element, UIElement other)322         {323             return element.TransformToVisual(other)324               .Transform(new Point(0, 0));325         }326     }327 328     public class ItemFlyInAndOutAnimations329     {330         private Popup _popup;331 332         private Canvas _popupCanvas;333 334         private FrameworkElement _targetElement;335 336         private Point _targetElementPosition;337 338         private Image _targetElementClone;339 340         private Rectangle _backgroundMask;341 342         private static TimeSpan _flyInSpeed = TimeSpan.FromMilliseconds(200);343 344         private static TimeSpan _flyOutSpeed = TimeSpan.FromMilliseconds(300);345 346         public ItemFlyInAndOutAnimations()347         {348             // construct a popup, with a Canvas as its child349             _popup = new Popup();350             _popupCanvas = new Canvas();351             _popup.Child = _popupCanvas;352         }353 354         public static void TitleFlyIn(FrameworkElement title)355         {356             TranslateTransform trans = new TranslateTransform();357             trans.X = 300;358             trans.Y = -50;359             title.RenderTransform = trans;360 361             var sb = new Storyboard();362 363             // animate the X position364             var db = CreateDoubleAnimation(300, 0,365                 new SineEase(), trans, TranslateTransform.XProperty, _flyInSpeed);366             sb.Children.Add(db);367 368             // animate the Y position369             db = CreateDoubleAnimation(-100, 0,370                 new SineEase(), trans, TranslateTransform.YProperty, _flyInSpeed);371             sb.Children.Add(db);372 373             sb.Begin();374         }375 376         /// <summary>377         /// Animate the previously 'flown-out' element back to its original location.378         /// </summary>379         public void ItemFlyIn()380         {381             if (_popupCanvas.Children.Count != 2)382                 return;383 384             _popup.IsOpen = true;385             _backgroundMask.Opacity = 0.0;386 387             Image animatedImage = _popupCanvas.Children[1] as Image;388 389             var sb = new Storyboard();390 391             // animate the X position392             var db = CreateDoubleAnimation(_targetElementPosition.X - 100, _targetElementPosition.X,393                 new SineEase(),394                 _targetElementClone, Canvas.LeftProperty, _flyInSpeed);395             sb.Children.Add(db);396 397             // animate the Y position398             db = CreateDoubleAnimation(_targetElementPosition.Y - 50, _targetElementPosition.Y,399                 new SineEase(),400                 _targetElementClone, Canvas.TopProperty, _flyInSpeed);401             sb.Children.Add(db);402 403             sb.Completed += (s, e) =>404             {405                 // when the animation has finished, hide the popup once more406                 _popup.IsOpen = false;407 408                 // restore the element we have animated409                 _targetElement.Opacity = 1.0;410 411                 // and get rid of our clone412                 _popupCanvas.Children.Clear();413             };414 415             sb.Begin();416         }417 418 419         /// <summary>420         /// Animate the given element so that it flies off screen, fading 421         /// everything else that is on screen.422         /// </summary>423         public void ItemFlyOut(FrameworkElement element, Action action)424         {425             _targetElement = element;426             var rootElement = Application.Current.RootVisual as FrameworkElement;427 428             _backgroundMask = new Rectangle()429             {430                 Fill = new SolidColorBrush(Colors.Black),431                 Opacity = 0.0,432                 Width = rootElement.ActualWidth,433                 Height = rootElement.ActualHeight434             };435             _popupCanvas.Children.Add(_backgroundMask);436 437             _targetElementClone = new Image()438             {439                 Source = new WriteableBitmap(element, null)440             };441             _popupCanvas.Children.Add(_targetElementClone);442 443             _targetElementPosition = element.GetRelativePosition(rootElement);444             Canvas.SetTop(_targetElementClone, _targetElementPosition.Y);445             Canvas.SetLeft(_targetElementClone, _targetElementPosition.X);446 447             var sb = new Storyboard();448 449             // animate the X position450             var db = CreateDoubleAnimation(_targetElementPosition.X, _targetElementPosition.X + 500,451                 new SineEase() { EasingMode = EasingMode.EaseIn },452                 _targetElementClone, Canvas.LeftProperty, _flyOutSpeed);453             sb.Children.Add(db);454 455             // animate the Y position456             db = CreateDoubleAnimation(_targetElementPosition.Y, _targetElementPosition.Y + 50,457                 new SineEase() { EasingMode = EasingMode.EaseOut },458                 _targetElementClone, Canvas.TopProperty, _flyOutSpeed);459             sb.Children.Add(db);460 461             // fade out the other elements462             db = CreateDoubleAnimation(0, 1,463                 null, _backgroundMask, UIElement.OpacityProperty, _flyOutSpeed);464             sb.Children.Add(db);465 466             sb.Completed += (s, e2) =>467             {468                 action();469 470                 // hide the popup, by placing a task on the dispatcher queue, this471                 // should be executed after the navigation has occurred472                 element.Dispatcher.BeginInvoke(() =>473                 {474                     _popup.IsOpen = false;475                 });476             };477 478             // hide the element we have 'cloned' into the popup479             element.Opacity = 0.0;480 481             // open the popup482             _popup.IsOpen = true;483 484             // begin the animation485             sb.Begin();486         }487 488         public static DoubleAnimation CreateDoubleAnimation(double from, double to, IEasingFunction easing,489           DependencyObject target, object propertyPath, TimeSpan duration)490         {491             var db = new DoubleAnimation();492             db.To = to;493             db.From = from;494             db.EasingFunction = easing;495             db.Duration = duration;496             Storyboard.SetTarget(db, target);497             Storyboard.SetTargetProperty(db, new PropertyPath(propertyPath));498             return db;499         }500     }501 502 503     public class VisualTreeAdapter : ILinqTree<DependencyObject>504     {505         private DependencyObject _item;506 507         public VisualTreeAdapter(DependencyObject item)508         {509             _item = item;510         }511 512         public IEnumerable<DependencyObject> Children()513         {514             int childrenCount = VisualTreeHelper.GetChildrenCount(_item);515             for (int i = 0; i < childrenCount; i++)516             {517                 yield return VisualTreeHelper.GetChild(_item, i);518             }519         }520 521         public DependencyObject Parent522         {523             get524             {525                 return VisualTreeHelper.GetParent(_item);526             }527         }528     }529 530     public interface ILinqTree<T>531     {532         IEnumerable<T> Children();533 534         T Parent { get; }535     }536 537     public static class TreeExtensions538     {539         /// <summary>540         /// Returns a collection of descendant elements.541         /// </summary>542         public static IEnumerable<DependencyObject> Descendants(this DependencyObject item)543         {544             ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);545             foreach (var child in adapter.Children())546             {547                 yield return child;548 549                 foreach (var grandChild in child.Descendants())550                 {551                     yield return grandChild;552                 }553             }554         }555 556         /// <summary>557         /// Returns a collection containing this element and all descendant elements.558         /// </summary>559         public static IEnumerable<DependencyObject> DescendantsAndSelf(this DependencyObject item)560         {561             yield return item;562 563             foreach (var child in item.Descendants())564             {565                 yield return child;566             }567         }568 569         /// <summary>570         /// Returns a collection of ancestor elements.571         /// </summary>572         public static IEnumerable<DependencyObject> Ancestors(this DependencyObject item)573         {574             ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);575 576             var parent = adapter.Parent;577             while (parent != null)578             {579                 yield return parent;580                 adapter = new VisualTreeAdapter(parent);581                 parent = adapter.Parent;582             }583         }584 585         /// <summary>586         /// Returns a collection containing this element and all ancestor elements.587         /// </summary>588         public static IEnumerable<DependencyObject> AncestorsAndSelf(this DependencyObject item)589         {590             yield return item;591 592             foreach (var ancestor in item.Ancestors())593             {594                 yield return ancestor;595             }596         }597 598         /// <summary>599         /// Returns a collection of child elements.600         /// </summary>601         public static IEnumerable<DependencyObject> Elements(this DependencyObject item)602         {603             ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);604             foreach (var child in adapter.Children())605             {606                 yield return child;607             }608         }609 610         /// <summary>611         /// Returns a collection of the sibling elements before this node, in document order.612         /// </summary>613         public static IEnumerable<DependencyObject> ElementsBeforeSelf(this DependencyObject item)614         {615             if (item.Ancestors().FirstOrDefault() == null)616                 yield break;617             foreach (var child in item.Ancestors().First().Elements())618             {619                 if (child.Equals(item))620                     break;621                 yield return child;622             }623         }624 625         /// <summary>626         /// Returns a collection of the after elements after this node, in document order.627         /// </summary>628         public static IEnumerable<DependencyObject> ElementsAfterSelf(this DependencyObject item)629         {630             if (item.Ancestors().FirstOrDefault() == null)631                 yield break;632             bool afterSelf = false;633             foreach (var child in item.Ancestors().First().Elements())634             {635                 if (afterSelf)636                     yield return child;637 638                 if (child.Equals(item))639                     afterSelf = true;640             }641         }642 643         /// <summary>644         /// Returns a collection containing this element and all child elements.645         /// </summary>646         public static IEnumerable<DependencyObject> ElementsAndSelf(this DependencyObject item)647         {648             yield return item;649 650             foreach (var child in item.Elements())651             {652                 yield return child;653             }654         }655 656         /// <summary>657         /// Returns a collection of descendant elements which match the given type.658         /// </summary>659         public static IEnumerable<DependencyObject> Descendants<T>(this DependencyObject item)660         {661             return item.Descendants().Where(i => i is T).Cast<DependencyObject>();662         }663 664         /// <summary>665         /// Returns a collection of the sibling elements before this node, in document order666         /// which match the given type.667         /// </summary>668         public static IEnumerable<DependencyObject> ElementsBeforeSelf<T>(this DependencyObject item)669         {670             return item.ElementsBeforeSelf().Where(i => i is T).Cast<DependencyObject>();671         }672 673         /// <summary>674         /// Returns a collection of the after elements after this node, in document order675         /// which match the given type.676         /// </summary>677         public static IEnumerable<DependencyObject> ElementsAfterSelf<T>(this DependencyObject item)678         {679             return item.ElementsAfterSelf().Where(i => i is T).Cast<DependencyObject>();680         }681 682         /// <summary>683         /// Returns a collection containing this element and all descendant elements684         /// which match the given type.685         /// </summary>686         public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this DependencyObject item)687         {688             return item.DescendantsAndSelf().Where(i => i is T).Cast<DependencyObject>();689         }690 691         /// <summary>692         /// Returns a collection of ancestor elements which match the given type.693         /// </summary>694         public static IEnumerable<DependencyObject> Ancestors<T>(this DependencyObject item)695         {696             return item.Ancestors().Where(i => i is T).Cast<DependencyObject>();697         }698 699         /// <summary>700         /// Returns a collection containing this element and all ancestor elements701         /// which match the given type.702         /// </summary>703         public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this DependencyObject item)704         {705             return item.AncestorsAndSelf().Where(i => i is T).Cast<DependencyObject>();706         }707 708         /// <summary>709         /// Returns a collection of child elements which match the given type.710         /// </summary>711         public static IEnumerable<DependencyObject> Elements<T>(this DependencyObject item)712         {713             return item.Elements().Where(i => i is T).Cast<DependencyObject>();714         }715 716         /// <summary>717         /// Returns a collection containing this element and all child elements.718         /// which match the given type.719         /// </summary>720         public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this DependencyObject item)721         {722             return item.ElementsAndSelf().Where(i => i is T).Cast<DependencyObject>();723         }724 725     }726 727     public static class EnumerableTreeExtensions728     {729         /// <summary>730         /// Applies the given function to each of the items in the supplied731         /// IEnumerable.732         /// </summary>733         private static IEnumerable<DependencyObject> DrillDown(this IEnumerable<DependencyObject> items,734             Func<DependencyObject, IEnumerable<DependencyObject>> function)735         {736             foreach (var item in items)737             {738                 foreach (var itemChild in function(item))739                 {740                     yield return itemChild;741                 }742             }743         }744 745         /// <summary>746         /// Applies the given function to each of the items in the supplied747         /// IEnumerable, which match the given type.748         /// </summary>749         public static IEnumerable<DependencyObject> DrillDown<T>(this IEnumerable<DependencyObject> items,750             Func<DependencyObject, IEnumerable<DependencyObject>> function)751             where T : DependencyObject752         {753             foreach (var item in items)754             {755                 foreach (var itemChild in function(item))756                 {757                     if (itemChild is T)758                     {759                         yield return (T)itemChild;760                     }761                 }762             }763         }764 765         /// <summary>766         /// Returns a collection of descendant elements.767         /// </summary>768         public static IEnumerable<DependencyObject> Descendants(this IEnumerable<DependencyObject> items)769         {770             return items.DrillDown(i => i.Descendants());771         }772 773         /// <summary>774         /// Returns a collection containing this element and all descendant elements.775         /// </summary>776         public static IEnumerable<DependencyObject> DescendantsAndSelf(this IEnumerable<DependencyObject> items)777         {778             return items.DrillDown(i => i.DescendantsAndSelf());779         }780 781         /// <summary>782         /// Returns a collection of ancestor elements.783         /// </summary>784         public static IEnumerable<DependencyObject> Ancestors(this IEnumerable<DependencyObject> items)785         {786             return items.DrillDown(i => i.Ancestors());787         }788 789         /// <summary>790         /// Returns a collection containing this element and all ancestor elements.791         /// </summary>792         public static IEnumerable<DependencyObject> AncestorsAndSelf(this IEnumerable<DependencyObject> items)793         {794             return items.DrillDown(i => i.AncestorsAndSelf());795         }796 797         /// <summary>798         /// Returns a collection of child elements.799         /// </summary>800         public static IEnumerable<DependencyObject> Elements(this IEnumerable<DependencyObject> items)801         {802             return items.DrillDown(i => i.Elements());803         }804 805         /// <summary>806         /// Returns a collection containing this element and all child elements.807         /// </summary>808         public static IEnumerable<DependencyObject> ElementsAndSelf(this IEnumerable<DependencyObject> items)809         {810             return items.DrillDown(i => i.ElementsAndSelf());811         }812 813         /// <summary>814         /// Returns a collection of descendant elements which match the given type.815         /// </summary>816         public static IEnumerable<DependencyObject> Descendants<T>(this IEnumerable<DependencyObject> items)817             where T : DependencyObject818         {819             return items.DrillDown<T>(i => i.Descendants());820         }821 822         /// <summary>823         /// Returns a collection containing this element and all descendant elements.824         /// which match the given type.825         /// </summary>826         public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this IEnumerable<DependencyObject> items)827             where T : DependencyObject828         {829             return items.DrillDown<T>(i => i.DescendantsAndSelf());830         }831 832         /// <summary>833         /// Returns a collection of ancestor elements which match the given type.834         /// </summary>835         public static IEnumerable<DependencyObject> Ancestors<T>(this IEnumerable<DependencyObject> items)836             where T : DependencyObject837         {838             return items.DrillDown<T>(i => i.Ancestors());839         }840 841         /// <summary>842         /// Returns a collection containing this element and all ancestor elements.843         /// which match the given type.844         /// </summary>845         public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this IEnumerable<DependencyObject> items)846             where T : DependencyObject847         {848             return items.DrillDown<T>(i => i.AncestorsAndSelf());849         }850 851         /// <summary>852         /// Returns a collection of child elements which match the given type.853         /// </summary>854         public static IEnumerable<DependencyObject> Elements<T>(this IEnumerable<DependencyObject> items)855             where T : DependencyObject856         {857             return items.DrillDown<T>(i => i.Elements());858         }859 860         /// <summary>861         /// Returns a collection containing this element and all child elements.862         /// which match the given type.863         /// </summary>864         public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this IEnumerable<DependencyObject> items)865             where T : DependencyObject866         {867             return items.DrillDown<T>(i => i.ElementsAndSelf());868         }869     }
MetroInMotion.cs

使用的时候只需要这样

<toolkit:HubTile local:MetroInMotion.Tilt="1" Grid.Row="1" Grid.Column="1" Background="Red" Source="Assets/Tiles/FlipCycleTileSmall.png" Title="Metro" Message="This is Metro in App"/>

 

高级的磁贴

现在又说回开始菜单中的磁贴,在StandardTileData的属性中有一个Count专门表达这个App存在的通知数量,但是这个属性在后来的动态磁贴出现后而变得很少用(如下图左1),这部分内容并不是取代Count属性的动态磁贴的实现,而是关注另外个内置应用的磁贴——人脉(下图中间和右2)

这里的人脉磁贴需要用到之前提过的动画效果,源码不是我编写的,我只是在网上找到老外写了那么的一个控件,cs部分的注释已经加了,xmal的由于基础不好现在还没看得明白,代码全部都铺上来,效果如下图

 

 1     public class PeopleHubTileData : INotifyPropertyChanged   2     { 3         private ImageSource _ImageFront; 4         public ImageSource ImageFront 5         { 6             get 7             { 8                 return _ImageFront; 9             }10             set11             {12                 if (value != _ImageFront)13                 {14                     _ImageFront = value;15                     NotifyPropertyChanged("ImageFront");16                 }17             }18         }19 20         private ImageSource _ImageBack;21         public ImageSource ImageBack22         {23             get24             {25                 return _ImageBack;26             }27             set28             {29                 if (value != _ImageBack)30                 {31                     _ImageBack = value;32                     NotifyPropertyChanged("ImageBack");33                 }34             }35         }36 37         private Stretch _ImageStretch;38         public Stretch ImageStretch39         {40             get41             {42                 return _ImageStretch;43             }44             set45             {46                 if (value != _ImageStretch)47                 {48                     _ImageStretch = value;49                     NotifyPropertyChanged("ImageStretch");50                 }51             }52         }53 54 55       56 57         public event PropertyChangedEventHandler PropertyChanged;58         private void NotifyPropertyChanged(String propertyName)59         {60             PropertyChangedEventHandler handler = PropertyChanged;61             if (null != handler)62             {63                 handler(this, new PropertyChangedEventArgs(propertyName));64             }65         }66     }
PepoleHubTileData

 

 1     public class Tiles : DependencyObject 2     { 3         public Tiles() 4         { 5             this.CenterOfRotationY = 0.5; 6         } 7         public Tiles(object item) 8             : this() 9         {10             this.TileData = item;11         }12         public object TileData { get; set; }13 14         public double CenterOfRotationY { get; set; }15         public double ZIndex16         {17             get { return (int)GetValue(ZIndexProperty); }18             set { SetValue(ZIndexProperty, value); }19         }20         public static DependencyProperty ZIndexProperty = DependencyProperty.Register("ZIndex", typeof(int), typeof(Tiles), new PropertyMetadata(0));21         public double RotationX22         {23             get { return (double)GetValue(RotationXProperty); }24             set { SetValue(RotationXProperty, value); }25         }26         public static DependencyProperty RotationXProperty = DependencyProperty.Register("RotationX", typeof(double), typeof(Tiles), new PropertyMetadata(0.0));27        28 29       30     }
Tiles

 

  1     public class PeopleHubTile : ContentControl  2     {  3         #region Member variables  4         private int LastAnimatedTile = 0;  5         /// <summary>  6         /// 大磁贴起始位置选择完毕,可以开始制造大磁贴  7         /// </summary>  8         private bool isBigTileAnimationStarted = false;  9         /// <summary> 10         /// 表明给大磁贴选择了图片 11         /// </summary> 12         private bool isBitImageSelected = false; 13         /// <summary> 14         /// 大磁贴图片的索引 15         /// </summary> 16         private int BitImageSelectedIndex = 0; 17         /// <summary> 18         /// 累计翻动大磁贴时已经翻动了小磁贴的数目 19         /// </summary> 20         private int TileAnimateIndex = 0; 21         private int TileAnimationCount = 0; 22         /// <summary> 23         /// 所有磁贴进入就绪状态,可以开始选取大磁贴的起始位置 24         /// </summary> 25         private bool isReadyForBigTile = false; 26         private Random RandomTile = new Random(); 27         private DispatcherTimer dispatcherTimer = new DispatcherTimer(); 28         private List<String> ImageUrl = new List<string>()  29         { 30             "/Themes/Images/1.jpg", 31             "/Themes/Images/13.jpg", 32             "/Themes/Images/14.jpg", 33             "/Themes/Images/15.jpg", 34             "/Themes/Images/16.jpg", 35             "/Themes/Images/17.jpg", 36             "/Themes/Images/18.jpg", 37             "/Themes/Images/19.jpg", 38             "/Themes/Images/2.jpg", 39             "/Themes/Images/20.jpg", 40             "/Themes/Images/21.jpg", 41             "/Themes/Images/3.jpg", 42               43         }; 44  45  46  47         private ObservableCollection<Tiles> dataItems = new ObservableCollection<Tiles>() 48         { 49           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/1.jpg", UriKind.RelativeOrAbsolute)) }), 50           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/2.jpg", UriKind.RelativeOrAbsolute)) }), 51           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/13.jpg", UriKind.RelativeOrAbsolute)) }), 52           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/14.jpg", UriKind.RelativeOrAbsolute)) }), 53           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/15.jpg", UriKind.RelativeOrAbsolute)) }), 54           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/16.jpg", UriKind.RelativeOrAbsolute)) }), 55           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/17.jpg", UriKind.RelativeOrAbsolute)) }), 56           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/18.jpg", UriKind.RelativeOrAbsolute)) }), 57           new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/19.jpg", UriKind.RelativeOrAbsolute)) }), 58         }; 59         #endregion 60  61         #region Constructor 62         public PeopleHubTile() 63         { 64             DefaultStyleKey = typeof(PeopleHubTile); 65             Loaded += PeopleHubTile_Loaded; 66  67         } 68         #endregion 69  70         #region Methods 71         ListBox ItemsListBox; 72         void PeopleHubTile_Loaded(object sender, RoutedEventArgs e) 73         { 74             ///在generic中获取ListBox 附上各个Tilt在ListBox中 75             ItemsListBox = this.GetTemplateChild("ItemsListBox") as ListBox; 76             this.ItemsListBox.ItemsSource = dataItems; 77             //开启定时更换tile的任务 78             dispatcherTimer.Interval = TimeSpan.FromSeconds(2); 79             dispatcherTimer.Tick += dispatcherTimer_Tick; 80             dispatcherTimer.Start(); 81         } 82  83         void dispatcherTimer_Tick(object sender, EventArgs e) 84         { 85             //计数,如果是9个则尝试启动大磁贴 86             TileAnimationCount++; 87             if (TileAnimationCount > 9 && isReadyForBigTile == false) 88             { 89                 TileAnimationCount = 0; 90                 isReadyForBigTile = true; 91  92             } 93  94  95             int AnimateItem = 0; 96             Tiles AnimateTile = null; 97             //未启动大磁贴的操作 98             if (!isBigTileAnimationStarted) 99             {100                 AnimateItem = RandomTile.Next(this.dataItems.Count);101                 AnimateTile = this.dataItems[AnimateItem];102 103                 ///尝试启动大磁贴 并且当前是抽到变换的磁贴是允许作为大磁贴的第一个磁贴104                 ///它变换大磁贴时是从大磁贴的左上 右上 左下 右下来变换105                 if (isReadyForBigTile && (AnimateItem == 0 || AnimateItem == 1 || AnimateItem == 3 || AnimateItem == 4))106                 {107                     LastAnimatedTile = AnimateItem;108                     isBigTileAnimationStarted = true;109                     TileAnimateIndex = 0;110                    111                 }112 113                 ///用ZIndex来区分正面和反面114                 /// Animate small tiles115                 if (AnimateTile.ZIndex == 0)116                 {117                     //back tile118                     PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData;119 120                     int newImage = RandomTile.Next(ImageUrl.Count);121                     if (RandomTile.Next(20) > 5)122                         ItemData.ImageBack = new BitmapImage(new Uri(ImageUrl[newImage], UriKind.RelativeOrAbsolute));123                     else124                         ItemData.ImageBack = new BitmapImage(new Uri("", UriKind.RelativeOrAbsolute));125                 }126                 else if (AnimateTile.ZIndex != 0)127                 {128                     //front tile129                     PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData;130 131                     int newImage = RandomTile.Next(ImageUrl.Count);132                     if (RandomTile.Next(20) > 5)133                         ItemData.ImageFront = new BitmapImage(new Uri(ImageUrl[newImage], UriKind.RelativeOrAbsolute));134                     else135                         ItemData.ImageFront = new BitmapImage(new Uri("", UriKind.RelativeOrAbsolute));136 137 138                 }139 140 141             }142 143 144             ///已经启用大磁贴 145             else if (isBigTileAnimationStarted && TileAnimateIndex < 4)146             {147 148                 int[] LastTiles = new int[4];149                 //按照大磁贴其实位置来选出大磁贴所占用的小磁贴的索引150                 switch (LastAnimatedTile)151                 {152                     case 0:153                         LastTiles = new int[4] { 0, 1, 3, 4 };154                         break;155                     case 1:156                         LastTiles = new int[4] { 1, 2, 4, 5 };157                         break;158                     case 3:159                         LastTiles = new int[4] { 3, 4, 6, 7 };160                         break;161                     case 4:162                         LastTiles = new int[4] { 4, 5, 7, 8 };163                         break;164                     default:165                         break;166 167                 }168 169 170               171                 AnimateTile = this.dataItems[LastTiles[TileAnimateIndex]];172                 ///还没有生成大磁贴所用的图片时173                 if (!isBitImageSelected)174                 {175                     isBitImageSelected = true;176                     BitImageSelectedIndex = RandomTile.Next(ImageUrl.Count);177                 }178                 ///bmpWB是直接从资源列表中拿的图片179                 BitmapImage bmpWB = new BitmapImage(new Uri(ImageUrl[BitImageSelectedIndex], UriKind.RelativeOrAbsolute));180                 ///最终写到磁贴上的部分图片181                 WriteableBitmap ImageWB = new WriteableBitmap(bmpWB.PixelWidth, bmpWB.PixelHeight);182                 bmpWB.CreateOptions = BitmapCreateOptions.None;183                 ///整幅大磁贴的图片184                 WriteableBitmap imageBitMap = new WriteableBitmap(bmpWB);185 186                 switch (TileAnimateIndex)187                 {188                     case 0:189 190                         ImageWB = GetCropImage(imageBitMap, 0, 0, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2);191 192                         break;193                     case 1:194 195                         ImageWB = GetCropImage(imageBitMap, imageBitMap.PixelWidth / 2, 0, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2);196                         break;197                     case 2:198 199                         ImageWB = GetCropImage(imageBitMap, 0, imageBitMap.PixelHeight / 2, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2);200 201 202 203                         break;204                     case 3:205 206                         ImageWB = GetCropImage(imageBitMap, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2, imageBitMap.PixelWidth / 2, imageBitMap.PixelHeight / 2);207                         break;208                     default:209                         break;210 211                 }212                 ///通过累计数目来判断大磁贴是否完成213                 TileAnimateIndex++;214                 if (TileAnimateIndex > 3)215                 {216                     isBigTileAnimationStarted = false;217                     isReadyForBigTile = false;218                     isBitImageSelected = false;219                 }220 221 222                 //animate part of big tiles223                 if (AnimateTile.ZIndex == 0)224                 {225                     //back tile226                     PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData;227 228                     ItemData.ImageBack = ImageWB;229 230                 }231                 else if (AnimateTile.ZIndex != 0)232                 {233                     //front tile234                     PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData;235 236                     ItemData.ImageFront = ImageWB;237                 }238 239 240             }241             //tile animation 242             Storyboard MyStory = new Storyboard();243             DoubleAnimation MyDouble = new DoubleAnimation();244             MyDouble.From = AnimateTile.RotationX;245             MyDouble.To = AnimateTile.RotationX + 180;246             MyDouble.Duration = TimeSpan.FromSeconds(0.5);247             Storyboard.SetTarget(MyDouble, AnimateTile);248             Storyboard.SetTargetProperty(MyDouble, new PropertyPath(Tiles.RotationXProperty));249             MyStory.Children.Add(MyDouble);250 251             ObjectAnimationUsingKeyFrames MyObject = new ObjectAnimationUsingKeyFrames();252             DiscreteObjectKeyFrame MyKeyFrame = new DiscreteObjectKeyFrame();253             MyKeyFrame.KeyTime = TimeSpan.FromSeconds(0);254             MyKeyFrame.Value = AnimateTile.ZIndex;255             MyObject.KeyFrames.Add(MyKeyFrame);256            257             MyKeyFrame = new DiscreteObjectKeyFrame();258             MyKeyFrame.KeyTime = TimeSpan.FromSeconds(0.3);259             MyKeyFrame.Value = (AnimateTile.ZIndex == 0) ? 1 : 0;260             MyObject.KeyFrames.Add(MyKeyFrame);261             Storyboard.SetTarget(MyObject, AnimateTile);262             Storyboard.SetTargetProperty(MyObject, new PropertyPath(Tiles.ZIndexProperty));263             MyStory.Children.Add(MyObject);264             MyStory.Begin();265 266 267 268         }269 270         /// <summary>271         /// 利用数组copy,通过计算位图的位置来切割出部分的图片272         /// </summary>273         /// <param name="aBitmapSource"></param>274         /// <param name="XPoint"></param>275         /// <param name="YPoint"></param>276         /// <param name="aWidth"></param>277         /// <param name="aHeight"></param>278         /// <returns></returns>279         private static WriteableBitmap GetCropImage(WriteableBitmap aBitmapSource, int XPoint, int YPoint, int aWidth, int aHeight)280         {281             var SourceWidth = aBitmapSource.PixelWidth;282             var result = new WriteableBitmap(aWidth, aHeight);283             for (var x = 0; x < aHeight - 1; x++)284             {285                 var Index = XPoint + (YPoint + x) * SourceWidth;286                 var FinalIndex = x * aWidth;287                 Array.Copy(aBitmapSource.Pixels, Index, result.Pixels, FinalIndex, aWidth);288 289 290             }291             return result;292         }293         #endregion294 295         #region OnApplyTemplate296         public override void OnApplyTemplate()297         {298             base.OnApplyTemplate();299         }300         #endregion301     }
PepoleHubTile

 

 1 <ResourceDictionary 2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4     xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" 5     xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" 6     xmlns:local="clr-namespace:PeopleHubTileEx"> 7      8     <DataTemplate x:Key="DataTemplatePeopleHubTile"> 9         <Grid x:Name="TileGrid" Height="80" Width="80" >10             <Grid.Projection>11                 <PlaneProjection RotationX="{Binding RotationX}" CenterOfRotationY="{Binding CenterOfRotationX}">12                 </PlaneProjection>13             </Grid.Projection>14             <Grid x:Name="BackGrid" Canvas.ZIndex="{Binding ZIndex}" RenderTransformOrigin="0.5,0.5">15                 <Grid.RenderTransform>16                     <CompositeTransform ScaleY="-1"/>17                 </Grid.RenderTransform>18                 <Grid.Background>19                     <SolidColorBrush Color="Green"></SolidColorBrush>20                 </Grid.Background>21                 <Image Source="{Binding TileData.ImageBack}"  Stretch="Fill" />22             </Grid>23             <Grid x:Name="FrontGrid">24                 <Grid.Background>25                     <SolidColorBrush Color="Green"></SolidColorBrush>26                 </Grid.Background>27                 <Image  Source="{Binding TileData.ImageFront}"  Stretch="Fill" >28 29                 </Image>30             </Grid>31         </Grid>32     </DataTemplate>33   34     <Style TargetType="local:PeopleHubTile">35 36         <Setter Property="Template">37             <Setter.Value>38                 <ControlTemplate TargetType="local:PeopleHubTile">39                     <Grid>40                         <ListBox Name="ItemsListBox" Width="240" Height="240"41                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"42                  ScrollViewer.VerticalScrollBarVisibility="Disabled"43                  ItemTemplate="{StaticResource DataTemplatePeopleHubTile}">44                             <ListBox.ItemsPanel>45                                 <ItemsPanelTemplate>46                                     <toolkit:WrapPanel ItemHeight="80" ItemWidth="80" Orientation="Horizontal">47 48                                     </toolkit:WrapPanel>49                                 </ItemsPanelTemplate>50                             </ListBox.ItemsPanel>51                         </ListBox>52                        53                     </Grid>54 55                 </ControlTemplate>56             </Setter.Value>57         </Setter>58     </Style>59 </ResourceDictionary>
generic.xaml

使用的时候只需要以下面的形式则可。

            <PeopleHubTileControl:PeopleHubTile  VerticalAlignment="Center">                            </PeopleHubTileControl:PeopleHubTile>     

 

1楼shaomeng
支持一下!