[翻译]超炫列表动画的实现

本文主要介绍了如何与枢轴控件相结合优雅地滑入视图列表的内容 ,这个特效模拟Windows Phone 7的电子邮件应用程序的动画。

 

 

Windows Phone 7 的用户界面是以Metro Design Language为基础的,Metro Design Language偏向于清晰的印刷格式,超越谷歌浏览器的内容以及简单的方式。如果你想看到Metro设计的实际应用,我会强烈建议你去浏览Scott Barnes 在riagenic 的博客

  

  随便给大家提一下,最近卤面网做了一个“观欧战,乐分享-WP夏日总动员”的活动,参加了就有wp7手机拿,大家速度参加吧。

      当然,如果你有原创wp7的APP应用,也可以直接参加 Windows Phone 原创APP达人大赛 ,有机会拿lumia手机,同时也可以通过卤面网推广你的APP哦。

     昨天的欧洲杯你有观看没,速度来竞猜今天的比赛结果吧,“热战竞猜:第十一战 克罗地亚VS西班牙 意大利VS爱尔兰 ”,猜中有奖!

 

Silverlight 软件为WP 7 的基础元素(如按键及选框)提供了一套基础的Metro 形式,并且还有几个听筒的特殊控制系统,像是Pivot 和Panorama.这种控制系统使得创造一个基础的Metro界面更加容易,在这里我再次建议你去看一下Scott Barnes 的博客;Metro不是全部都是关于黑白的!然而,当使用一个WP7 phone 的时候,你大概会注意到那些本身带有的应用程序像是邮件,地图并且设置了更多的功能,列表流畅的进入视野或是当选定一个选项的时候关掉屏幕。Metro不仅仅是一个静态的形式,它也是在运动的。

 

这篇文章里面的代码实现了WP7的本地程序那种优美的幻灯片效果,就像下面看到的以一个支点从一个选项列表移动到另一个。这种代码已经在真实的电话硬件上测试过 

 

用这个代码去设置附加属性 ListAnimation.IsPivotAnimated 准确的使ListBox(or ItemsControl)包含在PivotItem 里。然后应用ListAnimation,AnimationLevel 到任何一个你希望让他生动的像列表划入视线的元素。在每个元素是动态的之前动画水平描述了这种延迟,例如,下面的这些审定使得总结紧跟着标题滑出,当然还有后面的日期。

 

<controls:PivotItem Header="BBC News">
  <!-- animating an ListBox -->
  <ListBox x:Name="bbcNews"
        local:ListAnimation.IsPivotAnimated="True">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <StackPanel Orientation="Vertical">
          <TextBlock Text="{Binding Title}"
                  Style="{StaticResource PhoneTextLargeStyle}"/>
          <TextBlock Text="{Binding Summary}"
                  Style="{StaticResource PhoneTextSmallStyle}"
                  local:ListAnimation.AnimationLevel="1"/>
          <TextBlock Text="{Binding Date}"
                  Style="{StaticResource PhoneTextSmallStyle}"
                  local:ListAnimation.AnimationLevel="2"/>
        </StackPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</controls:PivotItem>

达到这一目标的代码效果相对简单,所以我去展示这些多合一的趋势(删除所有常用的附加性能的公式化代码)

// handles changes in the IsPivotAnimated attached property
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 <= 0;
 
          // locate the stack panel that hosts the items
          VirtualizingStackPanel vsp = 
            list.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++)
          {
            // find all the item that have the AnimationLevel attached property set
            var lbi = list.ItemContainerGenerator.ContainerFromIndex(index);
            if (lbi == null)
              continue;
 
            vsp.Dispatcher.BeginInvoke(() =>
            {
                var animationTargets = lbi.Descendants()
                                       .Where(p => ListAnimation.GetAnimationLevel(p) > -1);
                foreach (FrameworkElement target in animationTargets)
                {
                  // trigger the required animation
                  GetAnimation(target, fromRight).Begin();
                }
            });
          };
        };
    };
}

当IsPivotAnimated首先被附加上的时候,LINQ-to-VisualTree 则用来锁定另一半PivotControl 以达到控制SelectionChanged项目。然而,这就是事情变得微妙的地方!如果一个中心点的控制仅仅包含两个PivotItems,一个选择的变换是不足以决定这个中心点是滑到左边还是右边!因此,我们需要去控制在SleletionChanged之后被开启的ManpulationCompleted项目去决定移动的方向。

 

一旦做了这个,我们就可以重复做列表里的所有项目,我们可以假设这个项目被托管在适用于ListBox的 VirtualizingStackPanel。每个项目都是可见的,另一个LINQ查询时用来发现任何将AnimationLevel的附加功能设置在上面。对于每个元素来说这种动画是可以创造也可以取消的。

 

Dispatcher,BeginInvoke 是用来开始动画的每一组以达到减少同时开始10-20个动画的影响。如果不用Dispatcher,当在真正的硬件上测试的时候将会有一个非常小但是却显而易见的颤抖在中心的一侧滑过,这个中心控制着这个动画是否被取消的重要位置。Dispatcher,BeginInvoke的应用意味着动画的建造以及启动现在就被列表里的每一个元素作为分开的任务组合起来。这意味着他们不必去执行这个作为独立的工作,允许手机开启几个动画然后完成其他任务。这个网络结果就是Pivot控制仍然在PivotItems之间很平滑的滑动。

 

创造必要的动画的代码就在下面,它们简单的加入了一个TranslateTransform 到这些元素并且创造了必要的动画。

/// <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  GetAnimation(
        FrameworkElement element, bool fromRight)
{
  double from = fromRight ? 80 : -80;
 
  Storyboard sb;
  double delay = (ListAnimation.GetAnimationLevel(element)) * 0.1 + 0.1;
 
  TranslateTransform trans = new TranslateTransform() { X = from };
  element.RenderTransform = trans;
 
  sb = new Storyboard();
  sb.BeginTime = TimeSpan.FromSeconds(delay);
 
  DoubleAnimation db = new DoubleAnimation();
  db.To = 0;
  db.From = from;
  db.EasingFunction = new SineEase();
  sb.Duration = db.Duration = TimeSpan.FromSeconds(0.8);
  sb.Children.Add(db);
  Storyboard.SetTarget(db, trans);
  Storyboard.SetTargetProperty(db, new PropertyPath("X"));
 
  return sb;
}

有趣的是,我尝试着用Artefact Animator,它有着非常简明的在后置代码里创造动画的API.然而,它通过直接设置性能使得每个元素成为动态的,所有它并不是非常实用与WP7,它可以再composition thread 上执行脚本用来提高性能。

 

你可以在ListAnimation.zip下载所有的代码资源。

 

原文请见

http://www.codeproject.com/Articles/197753/Metro-in-Motion-Part-1-Fluid-List-Animation

posted on 2012-06-18 09:18  yewenpeng  阅读(2235)  评论(6编辑  收藏  举报