简洁流畅,快速响应是Windows Phone的特点也是他的买点,我们在开发App时候,也要在手机有限的硬件性能上最大限度做到UI快速响应,微软在优化手机快速响应这块做了很多底层优化工作,其中有一个就是我们要讨论的Composite Thread。
Composite Thread这个中文叫组合线程也有翻译叫构图线程。很多人对Composite Thread都并不了解,也不知道这是干什么的,因为毕竟有关Composite Thread的有关资料确实很少,而且Composite Thread最早不是在WPF/Silverlight上实现的,而是首先在Silverlight For Windows Phone 7上实现的,等到Silverlight5才引进Composite Thread。
说了那么多,那Composite Thread到底是可以用来干嘛用的,一言以蔽之就是,可以把动画剥离开UI线程到组合线程上执行。
在WP7开发中,我们如果用让用户等待一个耗时操作的完成,会用一个ProgressBar控件来提示让用户等待流程的完成。但ProgressBar有一个很不好的地方就是他是在UI线程上执行的,如果这个时候UI线程大量计算操作时候,ProgressBar就会卡成白痴(注:WP8的ProgressBar没有这个问题)。这个时候,一个叫做PerformanceProgressBar控件就闪亮登场出来救场了,PerformanceProgressBar很对得起它的Performance的Title,因为他可以做到不卡UI线程。
可能大家很好奇,因为我们知道一般更改UI操作只能是在UI线程上进行,而动画也是在更改UI,但PerformanceProgressBar却可以在非UI线程上执行动画。PerformanceProgressBar是Silverlight For WP Toolkit下一个开源控件,我们可以直接通过源码来看到他的具体实现。
通过源码我们可以看到PerformanceProgressBar仅仅只是在ProgressBar的基础上重新定制了他的Style
<PerformanceProgressBar x:Name="customIndeterminateProgressBar" IsIndeterminate="true" Style="{StaticResource CustomIndeterminateProgressBar}" />
<Style x:Key="CustomIndeterminateProgressBar" TargetType="ProgressBar"> <Setter Property="Foreground" Value="{StaticResource PhoneAccentBrush}"/> <Setter Property="Background" Value="{StaticResource PhoneAccentBrush}"/> <Setter Property="Maximum" Value="100"/> <Setter Property="IsHitTestVisible" Value="False"/> <Setter Property="Padding" Value="{StaticResource PhoneHorizontalMargin}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ProgressBar"> <unsupported:RelativeAnimatingContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"> <unsupported:RelativeAnimatingContentControl.Resources> <ExponentialEase EasingMode="EaseOut" Exponent="1" x:Key="ProgressBarEaseOut"/> <ExponentialEase EasingMode="EaseOut" Exponent="1" x:Key="ProgressBarEaseIn"/> </unsupported:RelativeAnimatingContentControl.Resources> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Determinate"/> <VisualState x:Name="Indeterminate"> <Storyboard RepeatBehavior="Forever" Duration="00:00:04.4"> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="IndeterminateRoot"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DeterminateRoot"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Collapsed</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.0" Storyboard.TargetProperty="X" Storyboard.TargetName="R1TT"> <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0.1"/> <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="33.1" EasingFunction="{StaticResource ProgressBarEaseOut}"/> <LinearDoubleKeyFrame KeyTime="00:00:02.0" Value="66.1"/> <EasingDoubleKeyFrame KeyTime="00:00:02.5" Value="100.1" EasingFunction="{StaticResource ProgressBarEaseIn}"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.2" Storyboard.TargetProperty="X" Storyboard.TargetName="R2TT"> <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0.1"/> <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="33.1" EasingFunction="{StaticResource ProgressBarEaseOut}"/> <LinearDoubleKeyFrame KeyTime="00:00:02.0" Value="66.1"/> <EasingDoubleKeyFrame KeyTime="00:00:02.5" Value="100.1" EasingFunction="{StaticResource ProgressBarEaseIn}"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.4" Storyboard.TargetProperty="X" Storyboard.TargetName="R3TT"> <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0.1"/> <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="33.1" EasingFunction="{StaticResource ProgressBarEaseOut}"/> <LinearDoubleKeyFrame KeyTime="00:00:02.0" Value="66.1"/> <EasingDoubleKeyFrame KeyTime="00:00:02.5" Value="100.1" EasingFunction="{StaticResource ProgressBarEaseIn}"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.6" Storyboard.TargetProperty="X" Storyboard.TargetName="R4TT"> <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0.1"/> <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="33.1" EasingFunction="{StaticResource ProgressBarEaseOut}"/> <LinearDoubleKeyFrame KeyTime="00:00:02.0" Value="66.1"/> <EasingDoubleKeyFrame KeyTime="00:00:02.5" Value="100.1" EasingFunction="{StaticResource ProgressBarEaseIn}"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.8" Storyboard.TargetProperty="X" Storyboard.TargetName="R5TT"> <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0.1"/> <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="33.1" EasingFunction="{StaticResource ProgressBarEaseOut}"/> <LinearDoubleKeyFrame KeyTime="00:00:02.0" Value="66.1"/> <EasingDoubleKeyFrame KeyTime="00:00:02.5" Value="100.1" EasingFunction="{StaticResource ProgressBarEaseIn}"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="R1"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1"/> <DiscreteDoubleKeyFrame KeyTime="00:00:02.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.2" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="R2"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1"/> <DiscreteDoubleKeyFrame KeyTime="00:00:02.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.4" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="R3"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1"/> <DiscreteDoubleKeyFrame KeyTime="00:00:02.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.6" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="R4"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1"/> <DiscreteDoubleKeyFrame KeyTime="00:00:02.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.8" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="R5"> <DiscreteDoubleKeyFrame KeyTime="0" Value="1"/> <DiscreteDoubleKeyFrame KeyTime="00:00:02.5" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid> <Grid x:Name="DeterminateRoot" Margin="{TemplateBinding Padding}" Visibility="Visible"> <Rectangle x:Name="ProgressBarTrack" Fill="{TemplateBinding Background}" Height="4" Opacity="0.1"/> <Rectangle x:Name="ProgressBarIndicator" Fill="{TemplateBinding Foreground}" HorizontalAlignment="Left" Height="4"/> </Grid> <Border x:Name="IndeterminateRoot" Margin="{TemplateBinding Padding}" Visibility="Collapsed"> <Grid HorizontalAlignment="Left"> <Rectangle Fill="{TemplateBinding Foreground}" Height="4" IsHitTestVisible="False" Width="4" x:Name="R1" Opacity="0" CacheMode="BitmapCache"> <Rectangle.RenderTransform> <TranslateTransform x:Name="R1TT"/> </Rectangle.RenderTransform> </Rectangle> <Rectangle Fill="{TemplateBinding Foreground}" Height="4" IsHitTestVisible="False" Width="4" x:Name="R2" Opacity="0" CacheMode="BitmapCache"> <Rectangle.RenderTransform> <TranslateTransform x:Name="R2TT"/> </Rectangle.RenderTransform> </Rectangle> <Rectangle Fill="{TemplateBinding Foreground}" Height="4" IsHitTestVisible="False" Width="4" x:Name="R3" Opacity="0" CacheMode="BitmapCache"> <Rectangle.RenderTransform> <TranslateTransform x:Name="R3TT"/> </Rectangle.RenderTransform> </Rectangle> <Rectangle Fill="{TemplateBinding Foreground}" Height="4" IsHitTestVisible="False" Width="4" x:Name="R4" Opacity="0" CacheMode="BitmapCache"> <Rectangle.RenderTransform> <TranslateTransform x:Name="R4TT"/> </Rectangle.RenderTransform> </Rectangle> <Rectangle Fill="{TemplateBinding Foreground}" Height="4" IsHitTestVisible="False" Width="4" x:Name="R5" Opacity="0" CacheMode="BitmapCache"> <Rectangle.RenderTransform> <TranslateTransform x:Name="R5TT"/> </Rectangle.RenderTransform> </Rectangle> </Grid> </Border> </Grid> </unsupported:RelativeAnimatingContentControl> </ControlTemplate> </Setter.Value> </Setter> </Style>
在上面的Style里,ProgressBar的进度动画被封装在VisualStateGroup里,动画类型是DoubleAnimation,凡是DoubleAnimation的动画都会自动在Composite Thread上执行,从而做到不卡UI线程,Silverlight的动画就是在一个时间轴上不停改变依赖属性的值,也就是只要是动画Double类型的依赖属性(比如动画TranslateTransform X Y的值,动画UI的Opacity透明度),那么这些动画都会转到Composite Thread上执行。
总结
Composite Thread的存在是为了从UI线程剥离DoubleAnimation动画出来,提高UI快速响应能力。
DoubleAnima类型的动画会自动在Composite Thread上运行。
开发WP7时候,要用Toolkit下的PerformanceProgressBar替代系统的自带的ProgressBar来获得更好的UI体验。