WPF模拟电梯升降的缓动动画



如同Flash一样,WPF的亮点之一也在于其擅于表现平滑的动画效果,但以移动动画来说,仅凭简单的起始位置、目标位置,所产生的动画仍会非常生硬,这种动画忽略了移动开始时的加速过程与移动结束时的减速过程。

WPF在关键帧动画中提供了样条内插(Spline)型的关键帧,用以控制变化的速率曲线,但这东西实在有些复杂,且不够形象化,我研究很久也没明白如何实现“缓入——缓出”的效果,随后我从一本经典牛X却鲜有人知的过时的FlashMX教程中提取了一个缓动函数,我们将用这个函数来较真实地模拟电梯的升降行为。

至于那本牛X的书,我以后会为大家介绍,我个人认为,那本书应当作为平面动画编程的必修经典,而它却被粗烂地印刷,并一直摆在书店里不引人注目的位置。

进入正题:

首先在界面设计器中添加一个 Rectangle ,用以代表直升电梯,然后添加4 RadioButton 代表几个楼层的呼叫按钮。

稍加美化,即为下图所示:



RadioButton 的样式直接用来当电梯按钮,略显生硬,我们用下面的代码来美化一下它:

<Style TargetType="RadioButton">

                <Setter Property="Foreground" Value="#ADB7BD"/>

                <Setter Property="FontSize" Value="32"/>

                <Setter Property="Cursor" Value="Hand"/>

                <Setter Property="Template">

                    <Setter.Value>

                        <ControlTemplate TargetType="RadioButton">

                            <ContentPresenter/>

                            <ControlTemplate.Triggers>

                                <Trigger Property="IsChecked" Value="True">

                                    <Trigger.EnterActions>

                                        <BeginStoryboard>

                                            <Storyboard>

                                                <ColorAnimation To="#BD5E00" Duration="0:0:0.3" BeginTime="0:0:0.1" Storyboard.TargetProperty="(Button.Foreground).(SolidColorBrush.Color)"/>

                                            </Storyboard>

                                        </BeginStoryboard>

                                    </Trigger.EnterActions>

                                    <Trigger.ExitActions>

                                        <BeginStoryboard>

                                            <Storyboard>

                                                <ColorAnimation Duration="0:0:0.2" BeginTime="0:0:0.2" Storyboard.TargetProperty="(Button.Foreground).(SolidColorBrush.Color)"/>

                                            </Storyboard>

                                        </BeginStoryboard>

                                    </Trigger.ExitActions>

                                </Trigger>

                            </ControlTemplate.Triggers>

                        </ControlTemplate>

                    </Setter.Value>

                </Setter>

            </Style>




现在就比较帅了:



接下来为所有 RadioButton 添加统一的事件处理函数:



至此界面部分的全部代码如下,需要注意的是,所有元素都需要手动调整一下它们在Grid中的对齐方位,将其设为 Left Top。要知道,设计器会在你拖动它们的时候为其胡乱改变对其方位,这会使你的元素没有统一的定位标准,导致几乎没法用代码统一操控它们的位置。

<Window x:Class="缓动动画.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="Window1" Height="398" Width="381" x:Name="window" Background="{StaticResource back}" Loaded="window_Loaded">

        <Window.Resources>

            <Style TargetType="RadioButton">

                <Setter Property="Foreground" Value="#ADB7BD"/>

                <Setter Property="FontSize" Value="32"/>

                <Setter Property="Cursor" Value="Hand"/>

                <Setter Property="Template">

                    <Setter.Value>

                        <ControlTemplate TargetType="RadioButton">

                            <ContentPresenter/>

                            <ControlTemplate.Triggers>

                                <Trigger Property="IsChecked" Value="True">

                                    <Trigger.EnterActions>

                                        <BeginStoryboard>

                                            <Storyboard>

                                                <ColorAnimation To="#BD5E00" Duration="0:0:0.3" BeginTime="0:0:0.1" Storyboard.TargetProperty="(Button.Foreground).(SolidColorBrush.Color)"/>

                                            </Storyboard>

                                        </BeginStoryboard>

                                    </Trigger.EnterActions>

                                    <Trigger.ExitActions>

                                        <BeginStoryboard>

                                            <Storyboard>

                                                <ColorAnimation Duration="0:0:0.2" BeginTime="0:0:0.2" Storyboard.TargetProperty="(Button.Foreground).(SolidColorBrush.Color)"/>

                                            </Storyboard>

                                        </BeginStoryboard>

                                    </Trigger.ExitActions>

                                </Trigger>

                            </ControlTemplate.Triggers>

                        </ControlTemplate>

                    </Setter.Value>

                </Setter>

            </Style>

        </Window.Resources>

        <Grid>

            <Rectangle Fill="{StaticResource box}" Margin="45,265,0,0" Name="rectangle1" HorizontalAlignment="Left" Width="50" Height="50" VerticalAlignment="Top" />

            <RadioButton Checked="RadioButton_Checked" HorizontalAlignment="Left" Margin="123,205,0,0" VerticalAlignment="Top">1F</RadioButton>

            <RadioButton Checked="RadioButton_Checked" HorizontalAlignment="Left" Margin="123,125,0,0" VerticalAlignment="Top">2F</RadioButton>

            <RadioButton Checked="RadioButton_Checked" HorizontalAlignment="Left" Margin="123,45,0,0" VerticalAlignment="Top">3F</RadioButton>

            <RadioButton Checked="RadioButton_Checked" HorizontalAlignment="Left" Margin="123,285,0,0" VerticalAlignment="Top" IsChecked="True">B1</RadioButton>

        </Grid>

    </Window>


在后台书写事件处理函数代码:




请不要惊讶我使用中文命名函数,假如你看过我自己写的程序的源代码,你就会对此保持沉默。

这就是传说中的中文函数“开始升降”:



在这个函数中,我们创建了一个 Thickness 的关键帧动画,Thickness 通常用来代表一个元素的上下左右4边,比如 Border 四个边的粗细就是用 Thickness 描述的,而这里的 Margin 属性也是 Thickness
类型。


一些要点我写在了图里,这里就不累述了。

“缓动计算”,是的,又一个神奇的中文函数,你可以在下面完整的源码中看看它是如何运算的,至少我是对它的内容毫无兴趣。




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace 缓动动画
{
    /**////
    /// Window1.xaml 的交互逻辑
    ///
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void window_Loaded(object sender, RoutedEventArgs e)
        {

        }

        Storyboard s = new Storyboard();

        public double 缓动计算(TimeSpan 总时间, TimeSpan 现在时间, double 初始值, double 变动量)
        {
            var t = 现在时间.TotalSeconds /( 总时间.TotalSeconds / 2);
            if (t < 1) return (变动量 / 2 * Math.Pow(t, 5) + 初始值);
            return (变动量 / 2 * (Math.Pow(t - 2, 5) + 2) + 初始值);
        }

        private void RadioButton_Checked(object sender, RoutedEventArgs e)
        {
            开始升降((sender as RadioButton).Margin.Top - 5);
        }

        public void 开始升降(double 目标高度)
        {
            ThicknessAnimationUsingKeyFrames d = new ThicknessAnimationUsingKeyFrames();
            d.Duration = new Duration(TimeSpan.FromSeconds(Math.Abs((目标高度-rectangle1.Margin.Top)/80)));
            for (int i = 0; i < 100; i++)
            {
                var t = new Thickness();
                t.Left = rectangle1.Margin.Left;
                t.Right = rectangle1.Margin.Right;
                t.Bottom = rectangle1.Margin.Bottom;
                t.Top=缓动计算(d.Duration.TimeSpan, TimeSpan.FromSeconds(d.Duration.TimeSpan.TotalSeconds / 100 * i), rectangle1.Margin.Top, 目标高度-rectangle1.Margin.Top);
                d.KeyFrames.Add(new LinearThicknessKeyFrame(t, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(d.Duration.TimeSpan.TotalSeconds / 100 * i))));
            }
            s.Children.Add(d);
            Storyboard.SetTargetName(d, rectangle1.Name);
            Storyboard.SetTargetProperty(d, new PropertyPath(Rectangle.MarginProperty));
            s.Begin(rectangle1);
        }
    }
}





现在编译运行吧,随便点击一个楼层,你将会看到电梯平缓的起步,然后平缓的停靠在你所选的楼层上。




当然,即使是这样具有缓动效果的电梯,乘客也是很难生还的,但至少会比生硬的上飞下坠要好很多啦。
附件
您所在的用户组无法下载或查看附件