对每个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 }
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 }
使用的时候只需要这样
<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 }
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 }
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 }
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>
使用的时候只需要以下面的形式则可。
<PeopleHubTileControl:PeopleHubTile VerticalAlignment="Center"> </PeopleHubTileControl:PeopleHubTile>
- 1楼shaomeng
- 支持一下!