1. 程式人生 > >WPF學習(16)-高階動畫

WPF學習(16)-高階動畫

       基本動畫我已經會拉,比如按鈕寬度動畫變動,顏色線性改變,其實動畫的類有很多很多,對於高階動畫,就是要選擇正確的屬性去控制元素的變化,比如對於元素的變換,前面已經有了rendertransform,其實這個變換是可以多個一起的,比如可以使用transformgroup,放置多個變換。

         比如下個例子就是同時改變按鈕的X,Y方向的縮放比例,然後以按鈕的中心為原點做360度的轉動。

      <Button Content="Button" HorizontalAlignment="Left" Margin="178,149,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.5,0.5">
            <Button.RenderTransform >
                <TransformGroup >
                <RotateTransform></RotateTransform>
                <ScaleTransform></ScaleTransform>
                </TransformGroup>
            </Button.RenderTransform>
            <Button.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].Angle" From="0" To=" 360" Duration="0:0:10"></DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].ScaleX" From="0" To="1" Duration="0:0:10"></DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].ScaleY" From="0" To="1" Duration="0:0:10"></DoubleAnimation>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Button.Triggers>
        </Button>

     動態改變畫刷,對於一些圖形應用效果渲染特別好用,比如下面的例子,可以再加上滑鼠的位置,然後精確控制漸變畫刷的變動。

    <Canvas>
        <Ellipse Width="100" Height="100">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.5,0.5">
                    <GradientStop Color="AliceBlue" Offset="0"></GradientStop>
                    <GradientStop Color="Red" Offset="1"></GradientStop>
                </RadialGradientBrush>
            </Ellipse.Fill>
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <PointAnimation Storyboard.TargetProperty="Fill.GradientOrigin" From="0.7,0.3" To="0.3,0.7" Duration="0:0:6"></PointAnimation>
                                <ColorAnimation Storyboard.TargetProperty="Fill.GradientStops[1].Color" To="Black" Duration="0:0:6"></ColorAnimation>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>              
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>

       VisualBrush(視覺畫刷),可以把某一塊區域或者元素給複製下來,這個元素不一定是單個元素,可以是組合的,而且原來元素髮生變化,後面也會跟著變化的。例子如下

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <DockPanel Grid.Row="0" Name="dp1">
            <Button  DockPanel.Dock="Top">一個按鈕</Button>
            <TextBlock DockPanel.Dock="Top">一個文字</TextBlock>
            <ComboBox>
                <ComboBoxItem>洪波</ComboBoxItem>
                <ComboBoxItem>薛世海</ComboBoxItem>
            </ComboBox>
        </DockPanel>
        <Canvas Grid.Row="1">
            <Rectangle  Width="509" Height="151">
                <Rectangle.Fill>
                    <VisualBrush Visual="{Binding ElementName=dp1}"></VisualBrush>
                </Rectangle.Fill>
            </Rectangle>
        </Canvas>
    </Grid>

      高階相對於前面的第一個點,就是關鍵幀動畫,比如我們設定了一個屬性變化,肯定是線性的,如果想要實現更復雜的,比如顯式地控制某個時間拿到不一樣的效果,那麼我們就需要關鍵幀啦。下面的例子就是使用關鍵幀動畫去實現一個畫布上圓心的移動。

    <Canvas>
        <Ellipse Width="100" Height="100">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.5,0.5">
                    <GradientStop Color="AliceBlue" Offset="0"></GradientStop>
                    <GradientStop Color="Red" Offset="1"></GradientStop>
                </RadialGradientBrush>
            </Ellipse.Fill>
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <PointAnimationUsingKeyFrames Storyboard.TargetProperty="Fill.GradientOrigin">
                                    <LinearPointKeyFrame Value="0.6,0.6" KeyTime="0:0:2"></LinearPointKeyFrame>
                                    <LinearPointKeyFrame Value="0.7,0.7" KeyTime="0:0:4"></LinearPointKeyFrame>
                                    <LinearPointKeyFrame Value="0.3,0.3" KeyTime="0:0:6"></LinearPointKeyFrame>
                                </PointAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>

        接著就是基於路經的動畫,讓一個元素可以沿著Path物件動,這個需求我們用的比較多,因為高精度定位系統,我們會提前規劃好移動的路徑,也就是場景規劃,下面的例子就是一個定位物件沿著一個現有的路徑去移動。

	<Canvas>
	<Path Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" x:Name="path1" Width="395" Height="153.872" Data="M113,237 C124.7604,205.63893 133.63378,191.16277 170,175 197.51838,162.76961 215.60147,169.78624 247,181 260.92393,185.97283 274.86458,192.92708 288,200 300.77704,206.87995 310.08281,211.51593 319,224 334.30431,245.42603 342.97421,270.97421 362,290 371.41237,299.41237 378.08578,305.50134 390,312 402.79518,318.97919 404.90289,322.78857 421,321 449.07154,317.88094 465.70329,295.29671&#xd;&#xa;487,274 498.97214,262.02786 502.13338,259.05985 507,243" Canvas.Left="112.5" Canvas.Top="168.067"/>
		<Button x:Name="btn1" Width="109.5" Height="37" Content="開始移動" Click="btn1_Click" Canvas.Left="398" Canvas.Top="99"/>
        <Border x:Name="border1" Width="77" Height="55" Canvas.Left="75" Canvas.Top="99" Background="Transparent">
            <Image Source="234.png"></Image>
        </Border>
    </Canvas>
 Canvas.SetTop(this.border1, -this.border1.ActualHeight / 2);
            Canvas.SetLeft(this.border1, -this.border1.ActualWidth / 2);

            this.border1.RenderTransformOrigin = new Point(0.5, 0.5);
        
            TranslateTransform translate = new TranslateTransform();
            RotateTransform rotate = new RotateTransform();
            TransformGroup group = new TransformGroup();
            group.Children.Add(rotate);//先旋轉
            group.Children.Add(translate);//再平移
            this.border1.RenderTransform = group;

            NameScope.SetNameScope(this, new NameScope());
            this.RegisterName("translate", translate);
            this.RegisterName("rotate", rotate);

            DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
            animationX.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationX.Source = PathAnimationSource.X;
            animationX.Duration = new Duration(TimeSpan.FromSeconds(5));

            DoubleAnimationUsingPath animationY = new DoubleAnimationUsingPath();
            animationY.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationY.Source = PathAnimationSource.Y;
            animationY.Duration = animationX.Duration;

            DoubleAnimationUsingPath animationAngle = new DoubleAnimationUsingPath();
            animationAngle.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationAngle.Source = PathAnimationSource.Angle;
            animationAngle.Duration = animationX.Duration;

            Storyboard story = new Storyboard();
            story.RepeatBehavior = RepeatBehavior.Forever;
            story.AutoReverse = true;
            story.Children.Add(animationX);
            story.Children.Add(animationY);
            story.Children.Add(animationAngle);
            Storyboard.SetTargetName(animationX, "translate");
            Storyboard.SetTargetName(animationY, "translate");
            Storyboard.SetTargetName(animationAngle, "rotate");
            Storyboard.SetTargetProperty(animationX, new PropertyPath(TranslateTransform.XProperty));
            Storyboard.SetTargetProperty(animationY, new PropertyPath(TranslateTransform.YProperty));
            Storyboard.SetTargetProperty(animationAngle, new PropertyPath(RotateTransform.AngleProperty));

            story.Begin(this);

       基於幀的動畫意思就是一幀一幀的動,但是隻能純程式碼。

    <Grid>
        <StackPanel>
            <DockPanel>
                <Button DockPanel.Dock="Top" Click="Button_Click">開始</Button>
                <Button>結束</Button>
            </DockPanel>
            <Canvas Name="canvas" Height="200"></Canvas>
        </StackPanel>
    </Grid>
namespace WpfApplication4
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        private List<EllipseInfo> ellipses = new List<EllipseInfo>();
        private double accelerationY = 0.1;
        private int minStartSpeed = 1;
        private int maxStartingSpeed = 50;
        private double speedRatio = 0.1;
        private int minEllipses = 20;
        private int maxEllipses = 100;
        private int ellipseRadius = 10;
        private bool rendering = false;
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (!rendering)
            {
                ellipses.Clear();
                canvas.Children.Clear();
                CompositionTarget.Rendering += CompositionTarget_Rendering;
                rendering = true;
            }
        }
        private void StopRendering()
        {
            CompositionTarget.Rendering -= CompositionTarget_Rendering;
            rendering = false;
        }
        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (ellipses.Count==0)
            {
                int halfCanvasWidth = (int)canvas.ActualWidth / 2;
                Random rand = new Random();
                int ellipseCount = rand.Next(minEllipses, maxEllipses);
                for (int i = 0; i < ellipseCount; i++)
                {
                    Ellipse ellipse = new Ellipse();
                    ellipse.Fill = Brushes.LimeGreen;
                    ellipse.Width = ellipseRadius;
                    ellipse.Height = ellipseRadius;                   
                    Canvas.SetLeft(ellipse, halfCanvasWidth + rand.Next(-halfCanvasWidth, halfCanvasWidth));
                    Canvas.SetTop(ellipse, 0);
                    //新增到Canvas裡面
                    canvas.Children.Add(ellipse);
                    EllipseInfo info = new EllipseInfo(ellipse, speedRatio * rand.Next(minStartSpeed, maxStartingSpeed));
                    ellipses.Add(info);
                }
            }
            else
            {
                for (int i = ellipses.Count-1; i >=0; i--)
                {
                    EllipseInfo info = ellipses[i];
                    double top = Canvas.GetTop(info.Ellipse);
                    Canvas.SetTop(info.Ellipse,top+1*info.VelocityY);
                    if (top >= (canvas.ActualHeight - ellipseRadius * 2 - 10))
                    {
                        // This circle has reached the bottom.
                        // Stop animating it.
                        ellipses.Remove(info);
                    }
                    else
                    {
                        // Increase the velocity.
                        info.VelocityY += accelerationY;
                    }

                    if (ellipses.Count == 0)
                    {
                        // End the animation.
                        // There's no reason to keep calling this method
                        // if it has no work to do.
                        StopRendering();
                    }
                }
            }
        }
    }
}

        這樣就實現了一堆小球不斷地從上面滾到下面的動畫效果啦。