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

开始菜单的磁贴

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

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

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

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

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

 StandardTileData tileData = new StandardTileData()

 {

 Title = "Test Tile",

 BackContent = "The " + ShellTile.ActiveTiles.Count() + " One",

 BackTitle = ShellTile.ActiveTiles.Count().ToString(),

 Count = ShellTile.ActiveTiles.Count()

 };

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

 using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Collections.Generic;
using System.Windows.Controls.Primitives; #if WINDOWS_PHONE
using Microsoft.Phone.Controls;
#endif namespace ControlTiltEffect
{
/// <summary>
/// This code provides attached properties for adding a 'tilt' effect to all controls within a container.
/// </summary>
public class TiltEffect : DependencyObject
{ #region Constructor and Static Constructor
/// <summary>
/// This is not a constructable class, but it cannot be static because it derives from DependencyObject.
/// </summary>
private TiltEffect()
{
} /// <summary>
/// Initialize the static properties
/// </summary>
static TiltEffect()
{
// The tiltable items list.
TiltableItems = new List<Type>() { typeof(ButtonBase), typeof(ListBoxItem) };
UseLogarithmicEase = false;
} #endregion #region Fields and simple properties // These constants are the same as the built-in effects
/// <summary>
/// Maximum amount of tilt, in radians
/// </summary>
const double MaxAngle = 0.3; /// <summary>
/// Maximum amount of depression, in pixels
/// </summary>
const double MaxDepression = ; /// <summary>
/// Delay between releasing an element and the tilt release animation playing
/// </summary>
static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(); /// <summary>
/// Duration of tilt release animation
/// </summary>
static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(); /// <summary>
/// The control that is currently being tilted
/// </summary>
static FrameworkElement currentTiltElement; /// <summary>
/// The single instance of a storyboard used for all tilts
/// </summary>
static Storyboard tiltReturnStoryboard; /// <summary>
/// The single instance of an X rotation used for all tilts
/// </summary>
static DoubleAnimation tiltReturnXAnimation; /// <summary>
/// The single instance of a Y rotation used for all tilts
/// </summary>
static DoubleAnimation tiltReturnYAnimation; /// <summary>
/// The single instance of a Z depression used for all tilts
/// </summary>
static DoubleAnimation tiltReturnZAnimation; /// <summary>
/// The center of the tilt element
/// </summary>
static Point currentTiltElementCenter; /// <summary>
/// Whether the animation just completed was for a 'pause' or not
/// </summary>
static bool wasPauseAnimation = false; /// <summary>
/// Whether to use a slightly more accurate (but slightly slower) tilt animation easing function
/// </summary>
public static bool UseLogarithmicEase { get; set; } /// <summary>
/// Default list of items that are tiltable
/// </summary>
public static List<Type> TiltableItems { get; private set; } #endregion #region Dependency properties /// <summary>
/// Whether the tilt effect is enabled on a container (and all its children)
/// </summary>
public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
"IsTiltEnabled",
typeof(bool),
typeof(TiltEffect),
new PropertyMetadata(OnIsTiltEnabledChanged)
); /// <summary>
/// Gets the IsTiltEnabled dependency property from an object
/// </summary>
/// <param name="source">The object to get the property from</param>
/// <returns>The property's value</returns>
public static bool GetIsTiltEnabled(DependencyObject source) { return (bool)source.GetValue(IsTiltEnabledProperty); } /// <summary>
/// Sets the IsTiltEnabled dependency property on an object
/// </summary>
/// <param name="source">The object to set the property on</param>
/// <param name="value">The value to set</param>
public static void SetIsTiltEnabled(DependencyObject source, bool value) { source.SetValue(IsTiltEnabledProperty, value); } /// <summary>
/// Suppresses the tilt effect on a single control that would otherwise be tilted
/// </summary>
public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
"SuppressTilt",
typeof(bool),
typeof(TiltEffect),
null
); /// <summary>
/// Gets the SuppressTilt dependency property from an object
/// </summary>
/// <param name="source">The object to get the property from</param>
/// <returns>The property's value</returns>
public static bool GetSuppressTilt(DependencyObject source) { return (bool)source.GetValue(SuppressTiltProperty); } /// <summary>
/// Sets the SuppressTilt dependency property from an object
/// </summary>
/// <param name="source">The object to get the property from</param>
/// <returns>The property's value</returns>
public static void SetSuppressTilt(DependencyObject source, bool value) { source.SetValue(SuppressTiltProperty, value); } /// <summary>
/// Property change handler for the IsTiltEnabled dependency property
/// </summary>
/// <param name="target">The element that the property is atteched to</param>
/// <param name="args">Event args</param>
/// <remarks>
/// Adds or removes event handlers from the element that has been (un)registered for tilting
/// </remarks>
static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
if (target is FrameworkElement)
{
// Add / remove the event handler if necessary
if ((bool)args.NewValue == true)
{
(target as FrameworkElement).ManipulationStarted += TiltEffect_ManipulationStarted;
}
else
{
(target as FrameworkElement).ManipulationStarted -= TiltEffect_ManipulationStarted;
}
}
} #endregion #region Top-level manipulation event handlers /// <summary>
/// Event handler for ManipulationStarted
/// </summary>
/// <param name="sender">sender of the event - this will be the tilt container (eg, entire page)</param>
/// <param name="e">event args</param>
static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{ TryStartTiltEffect(sender as FrameworkElement, e);
} /// <summary>
/// Event handler for ManipulationDelta
/// </summary>
/// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param>
/// <param name="e">event args</param>
static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{ ContinueTiltEffect(sender as FrameworkElement, e);
} /// <summary>
/// Event handler for ManipulationCompleted
/// </summary>
/// <param name="sender">sender of the event - this will be the tilting object (eg a button)</param>
/// <param name="e">event args</param>
static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{ EndTiltEffect(currentTiltElement);
} #endregion #region Core tilt logic /// <summary>
/// Checks if the manipulation should cause a tilt, and if so starts the tilt effect
/// </summary>
/// <param name="source">The source of the manipulation (the tilt container, eg entire page)</param>
/// <param name="e">The args from the ManipulationStarted event</param>
static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e)
{
foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors())
{
foreach (Type t in TiltableItems)
{
if (t.IsAssignableFrom(ancestor.GetType()))
{
if ((bool)ancestor.GetValue(SuppressTiltProperty) != true)
{
// Use first child of the control, so that you can add transforms and not
// impact any transforms on the control itself
FrameworkElement element = VisualTreeHelper.GetChild(ancestor, ) as FrameworkElement;
FrameworkElement container = e.ManipulationContainer as FrameworkElement; if (element == null || container == null)
return; // Touch point relative to the element being tilted
Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin); // Center of the element being tilted
Point elementCenter = new Point(element.ActualWidth / , element.ActualHeight / ); // Camera adjustment
Point centerToCenterDelta = GetCenterToCenterDelta(element, source); BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);
return;
}
}
}
}
} /// <summary>
/// Computes the delta between the centre of an element and its container
/// </summary>
/// <param name="element">The element to compare</param>
/// <param name="container">The element to compare against</param>
/// <returns>A point that represents the delta between the two centers</returns>
static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
{
Point elementCenter = new Point(element.ActualWidth / , element.ActualHeight / );
Point containerCenter; #if WINDOWS_PHONE // Need to special-case the frame to handle different orientations
if (container is PhoneApplicationFrame)
{
PhoneApplicationFrame frame = container as PhoneApplicationFrame; // Switch width and height in landscape mode
if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
{ containerCenter = new Point(container.ActualHeight / , container.ActualWidth / );
}
else
containerCenter = new Point(container.ActualWidth / , container.ActualHeight / );
}
else
containerCenter = new Point(container.ActualWidth / , container.ActualHeight / );
#else containerCenter = new Point(container.ActualWidth / , container.ActualHeight / ); #endif Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter);
Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y); return result;
} /// <summary>
/// Begins the tilt effect by preparing the control and doing the initial animation
/// </summary>
/// <param name="element">The element to tilt </param>
/// <param name="touchPoint">The touch point, in element coordinates</param>
/// <param name="centerPoint">The center point of the element in element coordinates</param>
/// <param name="centerDelta">The delta between the <paramref name="element"/>'s center and
/// the container's center</param>
static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)
{ if (tiltReturnStoryboard != null)
StopTiltReturnStoryboardAndCleanup(); if (PrepareControlForTilt(element, centerDelta) == false)
return; currentTiltElement = element;
currentTiltElementCenter = centerPoint;
PrepareTiltReturnStoryboard(element); ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
} /// <summary>
/// Prepares a control to be tilted by setting up a plane projection and some event handlers
/// </summary>
/// <param name="element">The control that is to be tilted</param>
/// <param name="centerDelta">Delta between the element's center and the tilt container's</param>
/// <returns>true if successful; false otherwise</returns>
/// <remarks>
/// This method is conservative; it will fail any attempt to tilt a control that already
/// has a projection on it
/// </remarks>
static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)
{
// Prevents interference with any existing transforms
if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
return false; TranslateTransform transform = new TranslateTransform();
transform.X = centerDelta.X;
transform.Y = centerDelta.Y;
element.RenderTransform = transform; PlaneProjection projection = new PlaneProjection();
projection.GlobalOffsetX = - * centerDelta.X;
projection.GlobalOffsetY = - * centerDelta.Y;
element.Projection = projection; element.ManipulationDelta += TiltEffect_ManipulationDelta;
element.ManipulationCompleted += TiltEffect_ManipulationCompleted; return true;
} /// <summary>
/// Removes modifications made by PrepareControlForTilt
/// </summary>
/// <param name="element">THe control to be un-prepared</param>
/// <remarks>
/// This method is basic; it does not do anything to detect if the control being un-prepared
/// was previously prepared
/// </remarks>
static void RevertPrepareControlForTilt(FrameworkElement element)
{
element.ManipulationDelta -= TiltEffect_ManipulationDelta;
element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
element.Projection = null;
element.RenderTransform = null;
} /// <summary>
/// Creates the tilt return storyboard (if not already created) and targets it to the projection
/// </summary>
/// <param name="projection">the projection that should be the target of the animation</param>
static void PrepareTiltReturnStoryboard(FrameworkElement element)
{ if (tiltReturnStoryboard == null)
{
tiltReturnStoryboard = new Storyboard();
tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed; tiltReturnXAnimation = new DoubleAnimation();
Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
tiltReturnXAnimation.To = ;
tiltReturnXAnimation.Duration = TiltReturnAnimationDuration; tiltReturnYAnimation = new DoubleAnimation();
Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty));
tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
tiltReturnYAnimation.To = ;
tiltReturnYAnimation.Duration = TiltReturnAnimationDuration; tiltReturnZAnimation = new DoubleAnimation();
Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
tiltReturnZAnimation.To = ;
tiltReturnZAnimation.Duration = TiltReturnAnimationDuration; if (UseLogarithmicEase)
{
tiltReturnXAnimation.EasingFunction = new LogarithmicEase();
tiltReturnYAnimation.EasingFunction = new LogarithmicEase();
tiltReturnZAnimation.EasingFunction = new LogarithmicEase();
} tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
} Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
} /// <summary>
/// Continues a tilt effect that is currently applied to an element, presumably because
/// the user moved their finger
/// </summary>
/// <param name="element">The element being tilted</param>
/// <param name="e">The manipulation event args</param>
static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e)
{
FrameworkElement container = e.ManipulationContainer as FrameworkElement;
if (container == null || element == null)
return; Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin); // If touch moved outside bounds of element, then pause the tilt (but don't cancel it)
if (new Rect(, , currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
{ PauseTiltEffect();
return;
} // Apply the updated tilt effect
ApplyTiltEffect(currentTiltElement, e.ManipulationOrigin, currentTiltElementCenter);
} /// <summary>
/// Ends the tilt effect by playing the animation
/// </summary>
/// <param name="element">The element being tilted</param>
static void EndTiltEffect(FrameworkElement element)
{
if (element != null)
{
element.ManipulationCompleted -= TiltEffect_ManipulationCompleted;
element.ManipulationDelta -= TiltEffect_ManipulationDelta;
} if (tiltReturnStoryboard != null)
{
wasPauseAnimation = false;
if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
tiltReturnStoryboard.Begin();
}
else
StopTiltReturnStoryboardAndCleanup();
} /// <summary>
/// Handler for the storyboard complete event
/// </summary>
/// <param name="sender">sender of the event</param>
/// <param name="e">event args</param>
static void TiltReturnStoryboard_Completed(object sender, EventArgs e)
{
if (wasPauseAnimation)
ResetTiltEffect(currentTiltElement);
else
StopTiltReturnStoryboardAndCleanup();
} /// <summary>
/// Resets the tilt effect on the control, making it appear 'normal' again
/// </summary>
/// <param name="element">The element to reset the tilt on</param>
/// <remarks>
/// This method doesn't turn off the tilt effect or cancel any current
/// manipulation; it just temporarily cancels the effect
/// </remarks>
static void ResetTiltEffect(FrameworkElement element)
{
PlaneProjection projection = element.Projection as PlaneProjection;
projection.RotationY = ;
projection.RotationX = ;
projection.GlobalOffsetZ = ;
} /// <summary>
/// Stops the tilt effect and release resources applied to the currently-tilted control
/// </summary>
static void StopTiltReturnStoryboardAndCleanup()
{
if (tiltReturnStoryboard != null)
tiltReturnStoryboard.Stop(); RevertPrepareControlForTilt(currentTiltElement);
} /// <summary>
/// Pauses the tilt effect so that the control returns to the 'at rest' position, but doesn't
/// stop the tilt effect (handlers are still attached, etc.)
/// </summary>
static void PauseTiltEffect()
{
if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
{
tiltReturnStoryboard.Stop();
wasPauseAnimation = true;
tiltReturnStoryboard.Begin();
}
} /// <summary>
/// Resets the storyboard to not running
/// </summary>
private static void ResetTiltReturnStoryboard()
{
tiltReturnStoryboard.Stop();
wasPauseAnimation = false;
} /// <summary>
/// Applies the tilt effect to the control
/// </summary>
/// <param name="element">the control to tilt</param>
/// <param name="touchPoint">The touch point, in the container's coordinates</param>
/// <param name="centerPoint">The center point of the container</param>
static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
{
// Stop any active animation
ResetTiltReturnStoryboard(); // Get relative point of the touch in percentage of container size
Point normalizedPoint = new Point(
Math.Min(Math.Max(touchPoint.X / (centerPoint.X * ), ), ),
Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * ), ), )); // Shell values
double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
double angleMagnitude = xMagnitude + yMagnitude;
double xAngleContribution = xMagnitude + yMagnitude > ? xMagnitude / (xMagnitude + yMagnitude) : ; double angle = angleMagnitude * MaxAngle * / Math.PI;
double depression = ( - angleMagnitude) * MaxDepression; // RotationX and RotationY are the angles of rotations about the x- or y-*axis*;
// to achieve a rotation in the x- or y-*direction*, we need to swap the two.
// That is, a rotation to the left about the y-axis is a rotation to the left in the x-direction,
// and a rotation up about the x-axis is a rotation up in the y-direction.
PlaneProjection projection = element.Projection as PlaneProjection;
projection.RotationY = angle * xAngleContribution * xDirection;
projection.RotationX = angle * ( - xAngleContribution) * yDirection;
projection.GlobalOffsetZ = -depression;
} #endregion #region Custom easing function /// <summary>
/// Provides an easing function for the tilt return
/// </summary>
private class LogarithmicEase : EasingFunctionBase
{
/// <summary>
/// Computes the easing function
/// </summary>
/// <param name="normalizedTime">The time</param>
/// <returns>The eased value</returns>
protected override double EaseInCore(double normalizedTime)
{
return Math.Log(normalizedTime + ) / 0.693147181; // ln(t + 1) / ln(2)
}
} #endregion
} /// <summary>
/// Couple of simple helpers for walking the visual tree
/// </summary>
static class TreeHelpers
{
/// <summary>
/// Gets the ancestors of the element, up to the root
/// </summary>
/// <param name="node">The element to start from</param>
/// <returns>An enumerator of the ancestors</returns>
public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
{
FrameworkElement parent = node.GetVisualParent();
while (parent != null)
{
yield return parent;
parent = parent.GetVisualParent();
}
} /// <summary>
/// Gets the visual parent of the element
/// </summary>
/// <param name="node">The element to check</param>
/// <returns>The visual parent</returns>
public static FrameworkElement GetVisualParent(this FrameworkElement node)
{
return VisualTreeHelper.GetParent(node) as FrameworkElement;
}
}
}

TiltEffect.cs

     public static class MetroInMotion
{
#region AnimationLevel public static int GetAnimationLevel(DependencyObject obj)
{
return (int)obj.GetValue(AnimationLevelProperty);
} public static void SetAnimationLevel(DependencyObject obj, int value)
{
obj.SetValue(AnimationLevelProperty, value);
} public static readonly DependencyProperty AnimationLevelProperty =
DependencyProperty.RegisterAttached("AnimationLevel", typeof(int),
typeof(MetroInMotion), new PropertyMetadata(-)); #endregion #region Tilt public static double GetTilt(DependencyObject obj)
{
return (double)obj.GetValue(TiltProperty);
} public static void SetTilt(DependencyObject obj, double value)
{
obj.SetValue(TiltProperty, value);
} public static readonly DependencyProperty TiltProperty =
DependencyProperty.RegisterAttached("Tilt", typeof(double),
typeof(MetroInMotion), new PropertyMetadata(2.0, OnTiltChanged)); /// <summary>
/// The extent of the tilt action, the larger the number, the bigger the tilt
/// </summary>
private static double TiltAngleFactor = ; /// <summary>
/// The extent of the scaling action, the smaller the number, the greater the scaling.
/// </summary>
private static double ScaleFactor = ; private static void OnTiltChanged(DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
FrameworkElement targetElement = d as FrameworkElement; double tiltFactor = GetTilt(d); // create the required transformations
var projection = new PlaneProjection();
var scale = new ScaleTransform();
var translate = new TranslateTransform(); var transGroup = new TransformGroup();
transGroup.Children.Add(scale);
transGroup.Children.Add(translate); // associate with the target element
targetElement.Projection = projection;
targetElement.RenderTransform = transGroup;
targetElement.RenderTransformOrigin = new Point(0.5, 0.5); targetElement.MouseLeftButtonDown += (s, e) =>
{
var clickPosition = e.GetPosition(targetElement); // find the maximum of width / height
double maxDimension = Math.Max(targetElement.ActualWidth, targetElement.ActualHeight); // compute the normalised horizontal distance from the centre
double distanceFromCenterX = targetElement.ActualWidth / - clickPosition.X;
double normalisedDistanceX = * distanceFromCenterX / maxDimension; // rotate around the Y axis
projection.RotationY = normalisedDistanceX * TiltAngleFactor * tiltFactor; // compute the normalised vertical distance from the centre
double distanceFromCenterY = targetElement.ActualHeight / - clickPosition.Y;
double normalisedDistanceY = * distanceFromCenterY / maxDimension; // rotate around the X axis,
projection.RotationX = -normalisedDistanceY * TiltAngleFactor * tiltFactor; // find the distance to centre
double distanceToCentre = Math.Sqrt(normalisedDistanceX * normalisedDistanceX
+ normalisedDistanceY * normalisedDistanceY); // scale accordingly
double scaleVal = tiltFactor * ( - distanceToCentre) / ScaleFactor;
scale.ScaleX = - scaleVal;
scale.ScaleY = - scaleVal; // offset the plane transform
var rootElement = Application.Current.RootVisual as FrameworkElement;
var relativeToCentre = (targetElement.GetRelativePosition(rootElement).Y - rootElement.ActualHeight / ) / ;
translate.Y = -relativeToCentre;
projection.LocalOffsetY = +relativeToCentre; }; targetElement.ManipulationCompleted += (s, e) =>
{
var sb = new Storyboard();
sb.Children.Add(CreateAnimation(null, , 0.1, "RotationY", projection));
sb.Children.Add(CreateAnimation(null, , 0.1, "RotationX", projection));
sb.Children.Add(CreateAnimation(null, , 0.1, "ScaleX", scale));
sb.Children.Add(CreateAnimation(null, , 0.1, "ScaleY", scale));
sb.Begin(); translate.Y = ;
projection.LocalOffsetY = ;
}; } #endregion #region IsPivotAnimated public static bool GetIsPivotAnimated(DependencyObject obj)
{
return (bool)obj.GetValue(IsPivotAnimatedProperty);
} public static void SetIsPivotAnimated(DependencyObject obj, bool value)
{
obj.SetValue(IsPivotAnimatedProperty, value);
} public static readonly DependencyProperty IsPivotAnimatedProperty =
DependencyProperty.RegisterAttached("IsPivotAnimated", typeof(bool),
typeof(MetroInMotion), new PropertyMetadata(false, OnIsPivotAnimatedChanged)); private static void OnIsPivotAnimatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
ItemsControl list = d as ItemsControl; list.Loaded += (s2, e2) =>
{
// locate the pivot control that this list is within
Pivot pivot = list.Ancestors<Pivot>().Single() as Pivot; // and its index within the pivot
int pivotIndex = pivot.Items.IndexOf(list.Ancestors<PivotItem>().Single()); bool selectionChanged = false; pivot.SelectionChanged += (s3, e3) =>
{
selectionChanged = true;
}; // handle manipulation events which occur when the user
// moves between pivot items
pivot.ManipulationCompleted += (s, e) =>
{
if (!selectionChanged)
return; selectionChanged = false; if (pivotIndex != pivot.SelectedIndex)
return; // determine which direction this tab will be scrolling in from
bool fromRight = e.TotalManipulation.Translation.X <= ; // iterate over each of the items in view
var items = list.GetItemsInView().ToList();
for (int index = ; index < items.Count; index++)
{
var lbi = items[index]; list.Dispatcher.BeginInvoke(() =>
{
var animationTargets = lbi.Descendants()
.Where(p => MetroInMotion.GetAnimationLevel(p) > -);
foreach (FrameworkElement target in animationTargets)
{
// trigger the required animation
GetSlideAnimation(target, fromRight).Begin();
}
});
}; };
};
} #endregion /// <summary>
/// Animates each element in order, creating a 'peel' effect. The supplied action
/// is invoked when the animation ends.
/// </summary>
public static void Peel(this IEnumerable<FrameworkElement> elements, Action endAction)
{
var elementList = elements.ToList();
var lastElement = elementList.Last(); // iterate over all the elements, animating each of them
double delay = ;
foreach (FrameworkElement element in elementList)
{
var sb = GetPeelAnimation(element, delay); // add a Completed event handler to the last element
if (element.Equals(lastElement))
{
sb.Completed += (s, e) =>
{
endAction();
};
} sb.Begin();
delay += ;
}
} /// <summary>
/// Enumerates all the items that are currently visible in am ItemsControl. This implementation assumes
/// that a VirtualizingStackPanel is being used as the ItemsPanel.
/// </summary>
public static IEnumerable<FrameworkElement> GetItemsInView(this ItemsControl itemsControl)
{
// locate the stack panel that hosts the items
VirtualizingStackPanel vsp = itemsControl.Descendants<VirtualizingStackPanel>().First() as VirtualizingStackPanel; // iterate over each of the items in view
int firstVisibleItem = (int)vsp.VerticalOffset;
int visibleItemCount = (int)vsp.ViewportHeight;
for (int index = firstVisibleItem; index <= firstVisibleItem + visibleItemCount + ; index++)
{
var item = itemsControl.ItemContainerGenerator.ContainerFromIndex(index) as FrameworkElement;
if (item == null)
continue; yield return item;
}
} /// <summary>
/// Creates a PlaneProjection and associates it with the given element, returning
/// a Storyboard which will animate the PlaneProjection to 'peel' the item
/// from the screen.
/// </summary>
private static Storyboard GetPeelAnimation(FrameworkElement element, double delay)
{
Storyboard sb; var projection = new PlaneProjection()
{
CenterOfRotationX = -0.1
};
element.Projection = projection; // compute the angle of rotation required to make this element appear
// at a 90 degree angle at the edge of the screen.
var width = element.ActualWidth;
var targetAngle = Math.Atan( / (width / ));
targetAngle = targetAngle * / Math.PI; // animate the projection
sb = new Storyboard();
sb.BeginTime = TimeSpan.FromMilliseconds(delay);
sb.Children.Add(CreateAnimation(, -( - targetAngle), 0.3, "RotationY", projection));
sb.Children.Add(CreateAnimation(, , 0.3, "RotationZ", projection));
sb.Children.Add(CreateAnimation(, -, 0.3, "GlobalOffsetZ", projection));
return sb;
} private static DoubleAnimation CreateAnimation(double? from, double? to, double duration,
string targetProperty, DependencyObject target)
{
var db = new DoubleAnimation();
db.To = to;
db.From = from;
db.EasingFunction = new SineEase();
db.Duration = TimeSpan.FromSeconds(duration);
Storyboard.SetTarget(db, target);
Storyboard.SetTargetProperty(db, new PropertyPath(targetProperty));
return db;
} /// <summary>
/// Creates a TranslateTransform and associates it with the given element, returning
/// a Storyboard which will animate the TranslateTransform with a SineEase function
/// </summary>
private static Storyboard GetSlideAnimation(FrameworkElement element, bool fromRight)
{
double from = fromRight ? : -; Storyboard sb;
double delay = (MetroInMotion.GetAnimationLevel(element)) * 0.1 + 0.1; TranslateTransform trans = new TranslateTransform() { X = from };
element.RenderTransform = trans; sb = new Storyboard();
sb.BeginTime = TimeSpan.FromSeconds(delay);
sb.Children.Add(CreateAnimation(from, , 0.8, "X", trans));
return sb;
} } public static class ExtensionMethods
{
public static Point GetRelativePosition(this UIElement element, UIElement other)
{
return element.TransformToVisual(other)
.Transform(new Point(, ));
}
} public class ItemFlyInAndOutAnimations
{
private Popup _popup; private Canvas _popupCanvas; private FrameworkElement _targetElement; private Point _targetElementPosition; private Image _targetElementClone; private Rectangle _backgroundMask; private static TimeSpan _flyInSpeed = TimeSpan.FromMilliseconds(); private static TimeSpan _flyOutSpeed = TimeSpan.FromMilliseconds(); public ItemFlyInAndOutAnimations()
{
// construct a popup, with a Canvas as its child
_popup = new Popup();
_popupCanvas = new Canvas();
_popup.Child = _popupCanvas;
} public static void TitleFlyIn(FrameworkElement title)
{
TranslateTransform trans = new TranslateTransform();
trans.X = ;
trans.Y = -;
title.RenderTransform = trans; var sb = new Storyboard(); // animate the X position
var db = CreateDoubleAnimation(, ,
new SineEase(), trans, TranslateTransform.XProperty, _flyInSpeed);
sb.Children.Add(db); // animate the Y position
db = CreateDoubleAnimation(-, ,
new SineEase(), trans, TranslateTransform.YProperty, _flyInSpeed);
sb.Children.Add(db); sb.Begin();
} /// <summary>
/// Animate the previously 'flown-out' element back to its original location.
/// </summary>
public void ItemFlyIn()
{
if (_popupCanvas.Children.Count != )
return; _popup.IsOpen = true;
_backgroundMask.Opacity = 0.0; Image animatedImage = _popupCanvas.Children[] as Image; var sb = new Storyboard(); // animate the X position
var db = CreateDoubleAnimation(_targetElementPosition.X - , _targetElementPosition.X,
new SineEase(),
_targetElementClone, Canvas.LeftProperty, _flyInSpeed);
sb.Children.Add(db); // animate the Y position
db = CreateDoubleAnimation(_targetElementPosition.Y - , _targetElementPosition.Y,
new SineEase(),
_targetElementClone, Canvas.TopProperty, _flyInSpeed);
sb.Children.Add(db); sb.Completed += (s, e) =>
{
// when the animation has finished, hide the popup once more
_popup.IsOpen = false; // restore the element we have animated
_targetElement.Opacity = 1.0; // and get rid of our clone
_popupCanvas.Children.Clear();
}; sb.Begin();
} /// <summary>
/// Animate the given element so that it flies off screen, fading
/// everything else that is on screen.
/// </summary>
public void ItemFlyOut(FrameworkElement element, Action action)
{
_targetElement = element;
var rootElement = Application.Current.RootVisual as FrameworkElement; _backgroundMask = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Black),
Opacity = 0.0,
Width = rootElement.ActualWidth,
Height = rootElement.ActualHeight
};
_popupCanvas.Children.Add(_backgroundMask); _targetElementClone = new Image()
{
Source = new WriteableBitmap(element, null)
};
_popupCanvas.Children.Add(_targetElementClone); _targetElementPosition = element.GetRelativePosition(rootElement);
Canvas.SetTop(_targetElementClone, _targetElementPosition.Y);
Canvas.SetLeft(_targetElementClone, _targetElementPosition.X); var sb = new Storyboard(); // animate the X position
var db = CreateDoubleAnimation(_targetElementPosition.X, _targetElementPosition.X + ,
new SineEase() { EasingMode = EasingMode.EaseIn },
_targetElementClone, Canvas.LeftProperty, _flyOutSpeed);
sb.Children.Add(db); // animate the Y position
db = CreateDoubleAnimation(_targetElementPosition.Y, _targetElementPosition.Y + ,
new SineEase() { EasingMode = EasingMode.EaseOut },
_targetElementClone, Canvas.TopProperty, _flyOutSpeed);
sb.Children.Add(db); // fade out the other elements
db = CreateDoubleAnimation(, ,
null, _backgroundMask, UIElement.OpacityProperty, _flyOutSpeed);
sb.Children.Add(db); sb.Completed += (s, e2) =>
{
action(); // hide the popup, by placing a task on the dispatcher queue, this
// should be executed after the navigation has occurred
element.Dispatcher.BeginInvoke(() =>
{
_popup.IsOpen = false;
});
}; // hide the element we have 'cloned' into the popup
element.Opacity = 0.0; // open the popup
_popup.IsOpen = true; // begin the animation
sb.Begin();
} public static DoubleAnimation CreateDoubleAnimation(double from, double to, IEasingFunction easing,
DependencyObject target, object propertyPath, TimeSpan duration)
{
var db = new DoubleAnimation();
db.To = to;
db.From = from;
db.EasingFunction = easing;
db.Duration = duration;
Storyboard.SetTarget(db, target);
Storyboard.SetTargetProperty(db, new PropertyPath(propertyPath));
return db;
}
} public class VisualTreeAdapter : ILinqTree<DependencyObject>
{
private DependencyObject _item; public VisualTreeAdapter(DependencyObject item)
{
_item = item;
} public IEnumerable<DependencyObject> Children()
{
int childrenCount = VisualTreeHelper.GetChildrenCount(_item);
for (int i = ; i < childrenCount; i++)
{
yield return VisualTreeHelper.GetChild(_item, i);
}
} public DependencyObject Parent
{
get
{
return VisualTreeHelper.GetParent(_item);
}
}
} public interface ILinqTree<T>
{
IEnumerable<T> Children(); T Parent { get; }
} public static class TreeExtensions
{
/// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<DependencyObject> Descendants(this DependencyObject item)
{
ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);
foreach (var child in adapter.Children())
{
yield return child; foreach (var grandChild in child.Descendants())
{
yield return grandChild;
}
}
} /// <summary>
/// Returns a collection containing this element and all descendant elements.
/// </summary>
public static IEnumerable<DependencyObject> DescendantsAndSelf(this DependencyObject item)
{
yield return item; foreach (var child in item.Descendants())
{
yield return child;
}
} /// <summary>
/// Returns a collection of ancestor elements.
/// </summary>
public static IEnumerable<DependencyObject> Ancestors(this DependencyObject item)
{
ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item); var parent = adapter.Parent;
while (parent != null)
{
yield return parent;
adapter = new VisualTreeAdapter(parent);
parent = adapter.Parent;
}
} /// <summary>
/// Returns a collection containing this element and all ancestor elements.
/// </summary>
public static IEnumerable<DependencyObject> AncestorsAndSelf(this DependencyObject item)
{
yield return item; foreach (var ancestor in item.Ancestors())
{
yield return ancestor;
}
} /// <summary>
/// Returns a collection of child elements.
/// </summary>
public static IEnumerable<DependencyObject> Elements(this DependencyObject item)
{
ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);
foreach (var child in adapter.Children())
{
yield return child;
}
} /// <summary>
/// Returns a collection of the sibling elements before this node, in document order.
/// </summary>
public static IEnumerable<DependencyObject> ElementsBeforeSelf(this DependencyObject item)
{
if (item.Ancestors().FirstOrDefault() == null)
yield break;
foreach (var child in item.Ancestors().First().Elements())
{
if (child.Equals(item))
break;
yield return child;
}
} /// <summary>
/// Returns a collection of the after elements after this node, in document order.
/// </summary>
public static IEnumerable<DependencyObject> ElementsAfterSelf(this DependencyObject item)
{
if (item.Ancestors().FirstOrDefault() == null)
yield break;
bool afterSelf = false;
foreach (var child in item.Ancestors().First().Elements())
{
if (afterSelf)
yield return child; if (child.Equals(item))
afterSelf = true;
}
} /// <summary>
/// Returns a collection containing this element and all child elements.
/// </summary>
public static IEnumerable<DependencyObject> ElementsAndSelf(this DependencyObject item)
{
yield return item; foreach (var child in item.Elements())
{
yield return child;
}
} /// <summary>
/// Returns a collection of descendant elements which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> Descendants<T>(this DependencyObject item)
{
return item.Descendants().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection of the sibling elements before this node, in document order
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> ElementsBeforeSelf<T>(this DependencyObject item)
{
return item.ElementsBeforeSelf().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection of the after elements after this node, in document order
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> ElementsAfterSelf<T>(this DependencyObject item)
{
return item.ElementsAfterSelf().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection containing this element and all descendant elements
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this DependencyObject item)
{
return item.DescendantsAndSelf().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection of ancestor elements which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> Ancestors<T>(this DependencyObject item)
{
return item.Ancestors().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection containing this element and all ancestor elements
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this DependencyObject item)
{
return item.AncestorsAndSelf().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection of child elements which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> Elements<T>(this DependencyObject item)
{
return item.Elements().Where(i => i is T).Cast<DependencyObject>();
} /// <summary>
/// Returns a collection containing this element and all child elements.
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this DependencyObject item)
{
return item.ElementsAndSelf().Where(i => i is T).Cast<DependencyObject>();
} } public static class EnumerableTreeExtensions
{
/// <summary>
/// Applies the given function to each of the items in the supplied
/// IEnumerable.
/// </summary>
private static IEnumerable<DependencyObject> DrillDown(this IEnumerable<DependencyObject> items,
Func<DependencyObject, IEnumerable<DependencyObject>> function)
{
foreach (var item in items)
{
foreach (var itemChild in function(item))
{
yield return itemChild;
}
}
} /// <summary>
/// Applies the given function to each of the items in the supplied
/// IEnumerable, which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> DrillDown<T>(this IEnumerable<DependencyObject> items,
Func<DependencyObject, IEnumerable<DependencyObject>> function)
where T : DependencyObject
{
foreach (var item in items)
{
foreach (var itemChild in function(item))
{
if (itemChild is T)
{
yield return (T)itemChild;
}
}
}
} /// <summary>
/// Returns a collection of descendant elements.
/// </summary>
public static IEnumerable<DependencyObject> Descendants(this IEnumerable<DependencyObject> items)
{
return items.DrillDown(i => i.Descendants());
} /// <summary>
/// Returns a collection containing this element and all descendant elements.
/// </summary>
public static IEnumerable<DependencyObject> DescendantsAndSelf(this IEnumerable<DependencyObject> items)
{
return items.DrillDown(i => i.DescendantsAndSelf());
} /// <summary>
/// Returns a collection of ancestor elements.
/// </summary>
public static IEnumerable<DependencyObject> Ancestors(this IEnumerable<DependencyObject> items)
{
return items.DrillDown(i => i.Ancestors());
} /// <summary>
/// Returns a collection containing this element and all ancestor elements.
/// </summary>
public static IEnumerable<DependencyObject> AncestorsAndSelf(this IEnumerable<DependencyObject> items)
{
return items.DrillDown(i => i.AncestorsAndSelf());
} /// <summary>
/// Returns a collection of child elements.
/// </summary>
public static IEnumerable<DependencyObject> Elements(this IEnumerable<DependencyObject> items)
{
return items.DrillDown(i => i.Elements());
} /// <summary>
/// Returns a collection containing this element and all child elements.
/// </summary>
public static IEnumerable<DependencyObject> ElementsAndSelf(this IEnumerable<DependencyObject> items)
{
return items.DrillDown(i => i.ElementsAndSelf());
} /// <summary>
/// Returns a collection of descendant elements which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> Descendants<T>(this IEnumerable<DependencyObject> items)
where T : DependencyObject
{
return items.DrillDown<T>(i => i.Descendants());
} /// <summary>
/// Returns a collection containing this element and all descendant elements.
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this IEnumerable<DependencyObject> items)
where T : DependencyObject
{
return items.DrillDown<T>(i => i.DescendantsAndSelf());
} /// <summary>
/// Returns a collection of ancestor elements which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> Ancestors<T>(this IEnumerable<DependencyObject> items)
where T : DependencyObject
{
return items.DrillDown<T>(i => i.Ancestors());
} /// <summary>
/// Returns a collection containing this element and all ancestor elements.
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this IEnumerable<DependencyObject> items)
where T : DependencyObject
{
return items.DrillDown<T>(i => i.AncestorsAndSelf());
} /// <summary>
/// Returns a collection of child elements which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> Elements<T>(this IEnumerable<DependencyObject> items)
where T : DependencyObject
{
return items.DrillDown<T>(i => i.Elements());
} /// <summary>
/// Returns a collection containing this element and all child elements.
/// which match the given type.
/// </summary>
public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this IEnumerable<DependencyObject> items)
where T : DependencyObject
{
return items.DrillDown<T>(i => i.ElementsAndSelf());
}
}

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的由于基础不好现在还没看得明白,代码全部都铺上来,效果如下图

     public class PeopleHubTileData : INotifyPropertyChanged
{
private ImageSource _ImageFront;
public ImageSource ImageFront
{
get
{
return _ImageFront;
}
set
{
if (value != _ImageFront)
{
_ImageFront = value;
NotifyPropertyChanged("ImageFront");
}
}
} private ImageSource _ImageBack;
public ImageSource ImageBack
{
get
{
return _ImageBack;
}
set
{
if (value != _ImageBack)
{
_ImageBack = value;
NotifyPropertyChanged("ImageBack");
}
}
} private Stretch _ImageStretch;
public Stretch ImageStretch
{
get
{
return _ImageStretch;
}
set
{
if (value != _ImageStretch)
{
_ImageStretch = value;
NotifyPropertyChanged("ImageStretch");
}
}
} public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

PepoleHubTileData

     public class Tiles : DependencyObject
{
public Tiles()
{
this.CenterOfRotationY = 0.5;
}
public Tiles(object item)
: this()
{
this.TileData = item;
}
public object TileData { get; set; } public double CenterOfRotationY { get; set; }
public double ZIndex
{
get { return (int)GetValue(ZIndexProperty); }
set { SetValue(ZIndexProperty, value); }
}
public static DependencyProperty ZIndexProperty = DependencyProperty.Register("ZIndex", typeof(int), typeof(Tiles), new PropertyMetadata());
public double RotationX
{
get { return (double)GetValue(RotationXProperty); }
set { SetValue(RotationXProperty, value); }
}
public static DependencyProperty RotationXProperty = DependencyProperty.Register("RotationX", typeof(double), typeof(Tiles), new PropertyMetadata(0.0)); }

Tiles

     public class PeopleHubTile : ContentControl
{
#region Member variables
private int LastAnimatedTile = ;
/// <summary>
/// 大磁贴起始位置选择完毕,可以开始制造大磁贴
/// </summary>
private bool isBigTileAnimationStarted = false;
/// <summary>
/// 表明给大磁贴选择了图片
/// </summary>
private bool isBitImageSelected = false;
/// <summary>
/// 大磁贴图片的索引
/// </summary>
private int BitImageSelectedIndex = ;
/// <summary>
/// 累计翻动大磁贴时已经翻动了小磁贴的数目
/// </summary>
private int TileAnimateIndex = ;
private int TileAnimationCount = ;
/// <summary>
/// 所有磁贴进入就绪状态,可以开始选取大磁贴的起始位置
/// </summary>
private bool isReadyForBigTile = false;
private Random RandomTile = new Random();
private DispatcherTimer dispatcherTimer = new DispatcherTimer();
private List<String> ImageUrl = new List<string>()
{
"/Themes/Images/1.jpg",
"/Themes/Images/13.jpg",
"/Themes/Images/14.jpg",
"/Themes/Images/15.jpg",
"/Themes/Images/16.jpg",
"/Themes/Images/17.jpg",
"/Themes/Images/18.jpg",
"/Themes/Images/19.jpg",
"/Themes/Images/2.jpg",
"/Themes/Images/20.jpg",
"/Themes/Images/21.jpg",
"/Themes/Images/3.jpg", }; private ObservableCollection<Tiles> dataItems = new ObservableCollection<Tiles>()
{
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/1.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/2.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/13.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/14.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/15.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/16.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/17.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/18.jpg", UriKind.RelativeOrAbsolute)) }),
new Tiles(new PeopleHubTileData(){ ImageFront = new BitmapImage(new Uri("/Themes/Images/19.jpg", UriKind.RelativeOrAbsolute)) }),
};
#endregion #region Constructor
public PeopleHubTile()
{
DefaultStyleKey = typeof(PeopleHubTile);
Loaded += PeopleHubTile_Loaded; }
#endregion #region Methods
ListBox ItemsListBox;
void PeopleHubTile_Loaded(object sender, RoutedEventArgs e)
{
///在generic中获取ListBox 附上各个Tilt在ListBox中
ItemsListBox = this.GetTemplateChild("ItemsListBox") as ListBox;
this.ItemsListBox.ItemsSource = dataItems;
//开启定时更换tile的任务
dispatcherTimer.Interval = TimeSpan.FromSeconds();
dispatcherTimer.Tick += dispatcherTimer_Tick;
dispatcherTimer.Start();
} void dispatcherTimer_Tick(object sender, EventArgs e)
{
//计数,如果是9个则尝试启动大磁贴
TileAnimationCount++;
if (TileAnimationCount > && isReadyForBigTile == false)
{
TileAnimationCount = ;
isReadyForBigTile = true; } int AnimateItem = ;
Tiles AnimateTile = null;
//未启动大磁贴的操作
if (!isBigTileAnimationStarted)
{
AnimateItem = RandomTile.Next(this.dataItems.Count);
AnimateTile = this.dataItems[AnimateItem]; ///尝试启动大磁贴 并且当前是抽到变换的磁贴是允许作为大磁贴的第一个磁贴
///它变换大磁贴时是从大磁贴的左上 右上 左下 右下来变换
if (isReadyForBigTile && (AnimateItem == || AnimateItem == || AnimateItem == || AnimateItem == ))
{
LastAnimatedTile = AnimateItem;
isBigTileAnimationStarted = true;
TileAnimateIndex = ; } ///用ZIndex来区分正面和反面
/// Animate small tiles
if (AnimateTile.ZIndex == )
{
//back tile
PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; int newImage = RandomTile.Next(ImageUrl.Count);
if (RandomTile.Next() > )
ItemData.ImageBack = new BitmapImage(new Uri(ImageUrl[newImage], UriKind.RelativeOrAbsolute));
else
ItemData.ImageBack = new BitmapImage(new Uri("", UriKind.RelativeOrAbsolute));
}
else if (AnimateTile.ZIndex != )
{
//front tile
PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; int newImage = RandomTile.Next(ImageUrl.Count);
if (RandomTile.Next() > )
ItemData.ImageFront = new BitmapImage(new Uri(ImageUrl[newImage], UriKind.RelativeOrAbsolute));
else
ItemData.ImageFront = new BitmapImage(new Uri("", UriKind.RelativeOrAbsolute)); } } ///已经启用大磁贴
else if (isBigTileAnimationStarted && TileAnimateIndex < )
{ int[] LastTiles = new int[];
//按照大磁贴其实位置来选出大磁贴所占用的小磁贴的索引
switch (LastAnimatedTile)
{
case :
LastTiles = new int[] { , , , };
break;
case :
LastTiles = new int[] { , , , };
break;
case :
LastTiles = new int[] { , , , };
break;
case :
LastTiles = new int[] { , , , };
break;
default:
break; } AnimateTile = this.dataItems[LastTiles[TileAnimateIndex]];
///还没有生成大磁贴所用的图片时
if (!isBitImageSelected)
{
isBitImageSelected = true;
BitImageSelectedIndex = RandomTile.Next(ImageUrl.Count);
}
///bmpWB是直接从资源列表中拿的图片
BitmapImage bmpWB = new BitmapImage(new Uri(ImageUrl[BitImageSelectedIndex], UriKind.RelativeOrAbsolute));
///最终写到磁贴上的部分图片
WriteableBitmap ImageWB = new WriteableBitmap(bmpWB.PixelWidth, bmpWB.PixelHeight);
bmpWB.CreateOptions = BitmapCreateOptions.None;
///整幅大磁贴的图片
WriteableBitmap imageBitMap = new WriteableBitmap(bmpWB); switch (TileAnimateIndex)
{
case : ImageWB = GetCropImage(imageBitMap, , , imageBitMap.PixelWidth / , imageBitMap.PixelHeight / ); break;
case : ImageWB = GetCropImage(imageBitMap, imageBitMap.PixelWidth / , , imageBitMap.PixelWidth / , imageBitMap.PixelHeight / );
break;
case : ImageWB = GetCropImage(imageBitMap, , imageBitMap.PixelHeight / , imageBitMap.PixelWidth / , imageBitMap.PixelHeight / ); break;
case : ImageWB = GetCropImage(imageBitMap, imageBitMap.PixelWidth / , imageBitMap.PixelHeight / , imageBitMap.PixelWidth / , imageBitMap.PixelHeight / );
break;
default:
break; }
///通过累计数目来判断大磁贴是否完成
TileAnimateIndex++;
if (TileAnimateIndex > )
{
isBigTileAnimationStarted = false;
isReadyForBigTile = false;
isBitImageSelected = false;
} //animate part of big tiles
if (AnimateTile.ZIndex == )
{
//back tile
PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; ItemData.ImageBack = ImageWB; }
else if (AnimateTile.ZIndex != )
{
//front tile
PeopleHubTileData ItemData = AnimateTile.TileData as PeopleHubTileData; ItemData.ImageFront = ImageWB;
} }
//tile animation
Storyboard MyStory = new Storyboard();
DoubleAnimation MyDouble = new DoubleAnimation();
MyDouble.From = AnimateTile.RotationX;
MyDouble.To = AnimateTile.RotationX + ;
MyDouble.Duration = TimeSpan.FromSeconds(0.5);
Storyboard.SetTarget(MyDouble, AnimateTile);
Storyboard.SetTargetProperty(MyDouble, new PropertyPath(Tiles.RotationXProperty));
MyStory.Children.Add(MyDouble); ObjectAnimationUsingKeyFrames MyObject = new ObjectAnimationUsingKeyFrames();
DiscreteObjectKeyFrame MyKeyFrame = new DiscreteObjectKeyFrame();
MyKeyFrame.KeyTime = TimeSpan.FromSeconds();
MyKeyFrame.Value = AnimateTile.ZIndex;
MyObject.KeyFrames.Add(MyKeyFrame); MyKeyFrame = new DiscreteObjectKeyFrame();
MyKeyFrame.KeyTime = TimeSpan.FromSeconds(0.3);
MyKeyFrame.Value = (AnimateTile.ZIndex == ) ? : ;
MyObject.KeyFrames.Add(MyKeyFrame);
Storyboard.SetTarget(MyObject, AnimateTile);
Storyboard.SetTargetProperty(MyObject, new PropertyPath(Tiles.ZIndexProperty));
MyStory.Children.Add(MyObject);
MyStory.Begin(); } /// <summary>
/// 利用数组copy,通过计算位图的位置来切割出部分的图片
/// </summary>
/// <param name="aBitmapSource"></param>
/// <param name="XPoint"></param>
/// <param name="YPoint"></param>
/// <param name="aWidth"></param>
/// <param name="aHeight"></param>
/// <returns></returns>
private static WriteableBitmap GetCropImage(WriteableBitmap aBitmapSource, int XPoint, int YPoint, int aWidth, int aHeight)
{
var SourceWidth = aBitmapSource.PixelWidth;
var result = new WriteableBitmap(aWidth, aHeight);
for (var x = ; x < aHeight - ; x++)
{
var Index = XPoint + (YPoint + x) * SourceWidth;
var FinalIndex = x * aWidth;
Array.Copy(aBitmapSource.Pixels, Index, result.Pixels, FinalIndex, aWidth); }
return result;
}
#endregion #region OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
#endregion
}

PepoleHubTile

 <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
xmlns:local="clr-namespace:PeopleHubTileEx"> <DataTemplate x:Key="DataTemplatePeopleHubTile">
<Grid x:Name="TileGrid" Height="80" Width="80" >
<Grid.Projection>
<PlaneProjection RotationX="{Binding RotationX}" CenterOfRotationY="{Binding CenterOfRotationX}">
</PlaneProjection>
</Grid.Projection>
<Grid x:Name="BackGrid" Canvas.ZIndex="{Binding ZIndex}" RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<CompositeTransform ScaleY="-1"/>
</Grid.RenderTransform>
<Grid.Background>
<SolidColorBrush Color="Green"></SolidColorBrush>
</Grid.Background>
<Image Source="{Binding TileData.ImageBack}" Stretch="Fill" />
</Grid>
<Grid x:Name="FrontGrid">
<Grid.Background>
<SolidColorBrush Color="Green"></SolidColorBrush>
</Grid.Background>
<Image Source="{Binding TileData.ImageFront}" Stretch="Fill" > </Image>
</Grid>
</Grid>
</DataTemplate> <Style TargetType="local:PeopleHubTile"> <Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PeopleHubTile">
<Grid>
<ListBox Name="ItemsListBox" Width="240" Height="240"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemTemplate="{StaticResource DataTemplatePeopleHubTile}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemHeight="80" ItemWidth="80" Orientation="Horizontal"> </toolkit:WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox> </Grid> </ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

generic.xaml

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

            <PeopleHubTileControl:PeopleHubTile  VerticalAlignment="Center">

            </PeopleHubTileControl:PeopleHubTile>     

WinPhone学习笔记(四)——磁贴的更多相关文章

  1. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  2. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

  3. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  4. Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  5. Typescript 学习笔记四:回忆ES5 中的类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  6. ES6学习笔记<四> default、rest、Multi-line Strings

    default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...

  7. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  8. python3.4学习笔记(四) 3.x和2.x的区别,持续更新

    python3.4学习笔记(四) 3.x和2.x的区别 在2.x中:print html,3.x中必须改成:print(html) import urllib2ImportError: No modu ...

  9. Go语言学习笔记四: 运算符

    Go语言学习笔记四: 运算符 这章知识好无聊呀,本来想跨过去,但没准有初学者要学,还是写写吧. 运算符种类 与你预期的一样,Go的特点就是啥都有,爱用哪个用哪个,所以市面上的运算符基本都有. 算术运算 ...

  10. 零拷贝详解 Java NIO学习笔记四(零拷贝详解)

    转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...

随机推荐

  1. hadoop学习笔记:zookeeper学习(上)

    在前面的文章里我多次提到zookeeper对于分布式系统开发的重要性,因此对zookeeper的学习是非常必要的.本篇博文主要是讲解zookeeper的安装和zookeeper的一些基本的应用,同时我 ...

  2. 基础调试命令 - wt (watch and trace)

    本文介绍windbg动态调试过程中一个非常有用的命令,wt的用法. wt命令 wt命令之所以称为wt是因为它是watch and trace的简称,即用来观察和跟踪的命令.这个命令一般用在动态调试而不 ...

  3. 《30天自制操作系统》笔记(06)——CPU的32位模式

    <30天自制操作系统>笔记(06)——CPU的32位模式 进度回顾 上一篇中实现了启用鼠标.键盘的功能.屏幕上会显示出用户按键.点击鼠标的情况.这是通过设置硬件的中断函数实现的,可以说硬件 ...

  4. js中setTimeout()的使用bug

    今天用setTimeout()时,遇到一个奇怪的现象,通过多方面的查询,最终解决了问题,这是setTimeout()设计的时候存在的一点点bug. 代码的作用主要是在三秒后自动关闭本浏览器窗口: 代码 ...

  5. 知方可补不足~SqlServer自动备份数据库及清理备份文件

    回到目录 对于SQLSERVER这个关系型数据库来说,为了保持数据的安全,备份是必须的,当你的一个误操作导致数据丢失,这可能是灾难性的,是不被允许发生的,这时,我们必须要做好定期的备份工作,如我们可以 ...

  6. DOM (Document Object Model)文档对象模型

    [理解下DOM] DOM——Document Object Mode.DOM是网页上XHTML中文档正文标题啊.段落.列表.样式.以及ID/class等所有其他数据的一个内部表示.我自己的理解是将网页 ...

  7. WPF入门教程系列十——布局之Border与ViewBox(五)

    九. Border Border 是一个装饰的控件,此控件绘制边框及背景,在 Border 中只能有一个子控件,若要显示多个子控件,需要将一个附加的 Panel 控件放置在父 Border 中.然后可 ...

  8. Java 线程 — ConcurrentHashMap

    ConcurrentHashMap ConcurrentHashMap 结构 采用了分段锁的方法提高COncurrentHashMap并发,一个map里面有一个Segment数组--即多个Segmen ...

  9. Jquery实现AJAX拦截

    前几天项目需要实现一个AJAX拦截,于是就用jquery写了一个,这里分享一下. 需求是这样的,ajax不是我来写,所有说我是不能动ajax的,并且我也不知道什么时候它会发生,为了方便项目经理让我把它 ...

  10. Android内存回收机制

    退出但不关闭: 这是Android对于Linux的优化.当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”.从而使得用户调用程序时能 ...