在写 Windows phone 应用性能优化(一)的时候,在 ListBox 的项加载的时候,添加了一些简单的动画。
其实在 Windows Phone 的应用中使用 Blend 设计动画是很容易的,并且在程序的交互中,增加一些动画
效果,用户会感觉用户体验非常的好,从而提升了用户对应用的印象评分。
本文的 demo 演示如何逐项的加载列表中的每一项。对于延时迭代加载列表中的项,通常会考虑使用 DispatherTimer,
但是如果设计的逻辑较多,需要的代码量会比较多,并且不好维护。这里使用 Rx(Reactive Extensions) 中的
Observable 类进行对 IObservable<T> 的创建。在 Rx 中 IObservable<T> (可观察序列)和 IObserver<T> (观察者)
是两个核心的元素,我研究了一段时间,感觉它在 .NET 平台是可以大有作为的,它的使用并不难,很多操作使用是以 LINQ
的扩展方法而使用的,重点是理解 (IObservable)的 “推”数据 和 (IEnumerable)的 “拉”数据的 数据源数据流向的不同。
以后还会继续研究的。之前翻译的有关 Rx 一篇文章
这个示例在页面中使用一个 Pivot 控件,两个 PivotItem 项都是使用 ListBox 作为模版,区别是:
1)第一个 PivotItem 项中的 ItemsPanel 模版使用默认的,在第二个 PivotItem 中
的 ListBox 中, 把 ItemsPanel 设置为了 WrapPanel,从而使 Item 可以流动布局:
<ListBox ItemsSource="{Binding}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <Controls:WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel>
2)两个 ListBox 中的 ItemTemplate 中的 StackPanel 在触发 Loaded 事件时,开始的动画不同。
MainPage 页面中 Pivot 控件的全部代码:
<phone:Pivot SelectionChanged="Pivot_SelectionChanged"> <phone:PivotItem Header="Stack"> <ListBox ItemsSource="{Binding}" > <ListBox.ItemTemplate> <DataTemplate> <StackPanel x:Name="stack" Orientation="Horizontal" Margin="10,30,0,0"> <StackPanel.Triggers> <EventTrigger RoutedEvent="StackPanel.Loaded"> <BeginStoryboard> <Storyboard x:Name="Storyboard1"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="-180"/> <EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"> <EasingDoubleKeyFrame.EasingFunction> <QuinticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="106"/> <EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"> <EasingDoubleKeyFrame.EasingFunction> <QuinticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationZ)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"> <EasingDoubleKeyFrame.EasingFunction> <QuinticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="246"/> <EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"> <EasingDoubleKeyFrame.EasingFunction> <QuinticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="0.4"/> <EasingDoubleKeyFrame KeyTime="0:0:3" Value="1"> <EasingDoubleKeyFrame.EasingFunction> <QuinticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="0.4"/> <EasingDoubleKeyFrame KeyTime="0:0:3" Value="1"> <EasingDoubleKeyFrame.EasingFunction> <QuinticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </StackPanel.Triggers> <StackPanel.RenderTransform> <CompositeTransform/> </StackPanel.RenderTransform> <StackPanel.Projection> <PlaneProjection/> </StackPanel.Projection> <Image VerticalAlignment="Top" Source="{Binding Photo}" Width="150"/> <TextBlock Text="{Binding Title}" Width="250" Foreground="Wheat" FontSize="25" Margin="10,0,0,0" TextWrapping="Wrap"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </phone:PivotItem> <phone:PivotItem Header="Wrap"> <ListBox ItemsSource="{Binding}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <Controls:WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <StackPanel x:Name="stack" Orientation="Horizontal" Margin="10,30,0,0"> <StackPanel.Triggers> <EventTrigger RoutedEvent="StackPanel.Loaded"> <BeginStoryboard> <Storyboard x:Name="Storyboard1"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.CenterOfRotationX)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:2" Value="1"> <EasingDoubleKeyFrame.EasingFunction> <QuarticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)" Storyboard.TargetName="stack"> <EasingDoubleKeyFrame KeyTime="0" Value="96"/> <EasingDoubleKeyFrame KeyTime="0:0:2" Value="0"> <EasingDoubleKeyFrame.EasingFunction> <QuarticEase EasingMode="EaseOut"/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </StackPanel.Triggers> <StackPanel.Projection> <PlaneProjection/> </StackPanel.Projection> <Image VerticalAlignment="Top" Source="{Binding Photo}" Width="200"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </phone:PivotItem> </phone:Pivot>
程序的运行效果:
在 MainPage 的 C# 页面的主要代码就不贴出来了,和 Windows phone 应用性能优化(一) 中的代码基本相同。
重点的代码,是当 Pivot 触发 Pivot_SelectionChanged 事件的时候,逐项加载 ObservableCollection 集合:
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) { ObservableCollection<News> NewsList2 = new ObservableCollection<News>(); this.DataContext = NewsList2; #region 实现 1 //IObservable<long> obser = Observable.Interval(TimeSpan.FromSeconds(0.3)).ObserveOnDispatcher(); //obser.Subscribe((i) => //{ // NewsList2.Add(NewsList[(int)i]); //}); #endregion #region 实现 2 // 方法含义: // GenerateWithTime(初始值, 循环条件, 传递给 Observer‘观察者’的值, 延迟时间, 迭代) // // 类似于 for循环:for(初始值;循环条件;迭代) IObservable<int> source = Observable.GenerateWithTime(0, i => i < NewsList.Count, i => i, i => TimeSpan.FromSeconds(.3), i => i + 1); // 在 Dispather 线程,每次接受 source 传递来的 i 值,即下面的 x source.ObserveOnDispatcher().Subscribe(x => { NewsList2.Add(NewsList[x]); Debug.WriteLine(x); }); #endregion }
把上面的 Observable.GenerateWithTime(...); 方法可以理解成:
for (int i = 0; i < count; i++) { // 当然,Observable 的时间延迟是在异步线程中完成的 Thread.Sleep(TimeSpan.FromSeconds(.3)); // 逻辑 }