原文:WPF界面设计技巧(7)—模拟电梯升降的缓动动画

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

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

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

    进入正题:

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

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

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

    Code
            <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。要知道,设计器会在你拖动它们的时候为其胡乱改变对其方位,这会使你的元素没有统一的定位标准,导致几乎没法用代码统一操控它们的位置。

    Code
    <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 类型。

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

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

    Code
    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 缓动动画
    {
        /**//// <summary>
        /// Window1.xaml 的交互逻辑
        /// </summary>
        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);
            }
        }
    }

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

    当然,即使是这样具有缓动效果的电梯,乘客也是很难生还的,但至少会比生硬的上飞下坠要好很多啦。

    源代码下载

WPF界面设计技巧(7)—模拟电梯升降的缓动动画的更多相关文章

  1. WPF界面设计技巧(8)—自制山寨版CheckListBox

    原文:WPF界面设计技巧(8)-自制山寨版CheckListBox 近年来IT市场山寨横行啊,我们今天也来发扬一下山寨精神,搞个自制的CheckListBox出来. 喏,CheckListBox 就是 ...

  2. WPF界面设计技巧(3)—实现不规则动画按钮

    原文:WPF界面设计技巧(3)-实现不规则动画按钮 发布了定义WPF按钮的教程后,有朋友问能否实现不规则形状的按钮,今天我们就来讲一下不规则按钮的制作. 不规则按钮的做法实际上和先前我们做不规则窗体的 ...

  3. WPF界面设计技巧(11)-认知流文档 & 小议WPF的野心

    原文:WPF界面设计技巧(11)-认知流文档 & 小议WPF的野心 流文档是WPF中的一种独特的文档承载格式,它的书写和呈现方式都很像HTML,它也几乎具备了HTML的绝大多数优势,并提供了更 ...

  4. WPF界面设计技巧(10)-样式的继承

    原文:WPF界面设计技巧(10)-样式的继承 PS:现在我的MailMail完工了,进入内测阶段了,终于可以腾出手来写写教程了哈,关于MailMail的介绍及内测程序索取:http://www.cnb ...

  5. WPF界面设计技巧(9)—使用UI自动化布局

    原文:WPF界面设计技巧(9)-使用UI自动化布局 最近一直没时间更新这系列文章,因为我一直在埋头编写我的第一个WPF应用程序:MailMail 今天开始编写附属的加密/解密工具,对UI自动化布局有些 ...

  6. WPF界面设计技巧(6)—玩玩数字墨水手绘涂鸦

    原文:WPF界面设计技巧(6)-玩玩数字墨水手绘涂鸦 想让你的程序支持鼠标及手写笔涂鸦吗?只要敲入“<InkCanvas/>”这几个字符,你就会领悟什么叫“很好很强大”,今天我们来做一个手 ...

  7. WPF界面设计技巧(5)—自定义列表项呈现内容

    原文:WPF界面设计技巧(5)-自定义列表项呈现内容 接续上次的程序,稍微改动一下原有样式,并添加一个数据模板,我们就可以达成下面这样的显示功能: 鼠标悬停于文件列表项上,会在工具提示中显示图像缩略图 ...

  8. WPF界面设计技巧(4)—自定义列表项样式

    原文:WPF界面设计技巧(4)-自定义列表项样式 有前面修改按钮样式的基础,我们可以尝试来定制一个即好看又好用的 ListBox ,今天先来讲“好看”部分. 打开 Microsoft Visual S ...

  9. WPF界面设计技巧(2)—自定义漂亮的按钮样式

    原文:WPF界面设计技巧(2)-自定义漂亮的按钮样式 上次做了个很酷的不规则窗体,这次我们来弄点好看的按钮出来,此次将采用纯代码来设计按钮样式,不需要 Microsoft Expression Des ...

随机推荐

  1. Android中吐司当前电池电量

    /** * * @author chrp * *土司当当电池电量 */ public class MainActivity extends Activity { class BtteryReceive ...

  2. Java字符串找出4个字节长度的字符

    不解释,直接上代码:  由于Iteye代码贴四个字节的UTF-8字符出错,特能图的方式发布几个特殊字符:  public class Byte4Check { public static void m ...

  3. unity3d ngui-TweenRotation-TweenPosition-TweenScale

    using UnityEngine; using System.Collections; public class TweenFlipCARDS : MonoBehaviour { private f ...

  4. iOS基本控制-UINavigationController 传统的价值观,代理传统价值观,正向传统价值观,反传统的价值观

    /*        程序过程:  1.创建一个根视图,一个二级视图  2,根视图NavigationItem.title = Root 二级视图NavigationItem.title = Secon ...

  5. Eclipse中导入第三方源码的问题和备用解决方案

    在前篇<配置BeanUtils包,同时也是对导入第三包的步骤说明>中,我已经将[commons-beanutils-1.9.2.jar]包导入,但是在使用BeanUtils进行日期转换的过 ...

  6. 积累的VC编程小技巧之工具提示

    1.用鼠标移动基于对话框的无标题栏程序的简单方法 void CVCTestDlg::OnLButtonDown(UINT nFlags, CPoint point) {    //一句话解决问题    ...

  7. [Android学习笔记]Fragment使用

    一.android.app.Fragment 与 android.support.v4.app.Fragment 区别 support.v4.app.Fragment是为了给低版本Android使用的 ...

  8. MS Project 2007 工期、工时、资源、固定单位、固定工期、固定工时

    0. 基本概念 工期:指完成每项项目任务所经历的实际时间,及开始日期和结束时期之差.Project中,工期的默认单位是天. 工时:指将任务分配给资源后,每个资源执行任务的工作时间.Project中,工 ...

  9. ubuntu10.10 tftp安装,配置,测试

    ubuntu10.10 tftp安装,配置,测试 成于坚持,败于止步 虽然ubuntu/centos/redhat都是linux,但是内核其中存在一定的修改,所以对于tftp服务器的安装存在不同的命令 ...

  10. 八皇后问题详细分析与解答(递归法解答,c#语言描述)

    八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题.该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或 ...