从自定义DoubleAnimation开始

研究了好多天WPF了, 在cnblogs写第一篇技术文~纪念一下,大家也支持一下 呵呵
虽然是从从自定义DoubleAnimation开始,不过重点MS应该是在后面对Freezable DependencyProperty等的讨论~笔记的一部分,写的比较乱,不过思路还算清楚

下面尝试自定义一个
DoubleAnimation
简单的准备,
一般的DoubleAnimation是线性的变换函数


横坐标表示时间的进度
, 范围从01 ,这个值可以从Clock获得,纵坐标表示Double数值的进度,这里也定义为从01,乘以实际的总路径后就获得当前路径

 

可能某些时候需要复杂一点的变化,比如用二次曲线吧(其实这个可以用其他方法很简单的实现,这里只是讨论自定义Animation的问题)
假设曲线为二次曲线 (设横坐标为x,则曲线方程为y=x*x) 非常简单的一个例子 

首先定义一个矩形

 

      <Rectangle Stroke="#FF000000" x:Name="myRectangle" Width="30" Height="30" HorizontalAlignment="Left" Margin="34,57.5,0,0" VerticalAlignment="Top">

        
<Rectangle.RenderTransform>

            
<TranslateTransform X="0" Y="0"/>

        
</Rectangle.RenderTransform>

        
<Rectangle.Fill>

          
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">

            
<GradientStop Color="#FF000000" Offset="0"/>

            
<GradientStop Color="#FFFFFFFF" Offset="1"/>

          
</LinearGradientBrush>

        
</Rectangle.Fill>

      
</Rectangle>

DoubleAnimation让他动起来:

 <Window.Triggers>

    
<EventTrigger RoutedEvent="Window.Loaded">

      
<BeginStoryboard>

        
<Storyboard>

          
<DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="RenderTransform.X"

                           Duration
="0:0:3" From="0" To="180"/>

        
</Storyboard>

      
</BeginStoryboard>

    
</EventTrigger>

 
</Window.Triggers>

下面开始自定义一个AnimationWPF提供了DoubleAnimationBase类,用于扩展。

先新建一个类, 取名QuadraticDoubleAnimation,继承于DoubleAnimationBase,并实现抽象函数

首先来实现CreateInstanceCore() 这里暂时返回一个本类的新的实例就行了

        protected override Freezable CreateInstanceCore()
        
{
            
return new QuadraticDoubleAnimation();
        }

然后实现GetCurrentValueCore(),前面提到过TimeClock会通过GetCurrentValue()方法获得当前的值,不过GetCurrentValue()方法除了完成计算值之外还会完成其他工作,因此不能够直接定义GetCurrentValue()方法,定义GetCurrentValueCore(),就行了.

我们首先计算整个delta,也就是动画全程的距离,再通过y=x*x*delta计算并返回当前,很简单的两个语句

        protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue, AnimationClock animationClock)
        {
            
double delta = To - From;
            
double x=animationClock.CurrentProgress.Value;
            
return x * x * delta;
        }

完成了这些,一切看起来都不错,应该可以运行了。在XAML添加应用,并把对DoubleAnimation的引用修改为对我们的QuadraticDoubleAnimation的引用。

Window属性中加入

xmlns:myAnimations="clr-namespace:myCustonAnimation"

Storyboard的代码:

        <Storyboard>

          
<myAnimations:QuadraticDoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="RenderTransform.X"

                           Duration
="0:0:3" From="0" To="180"/>

        
</Storyboard>

运行。编译情况不妙,没通过,原来ToFrom属性没有实现。看看DoubleAnimation类和DoubleAnimationBase类的实现
注意到Duration这样的属性是找得到的,实际上Duration属性是早在TimeLine里就实现了(继承关系TimeLine->AnimationTimeline->DoubleAnimationBase)但是From/To包括By属性是在DoubleAnimation类实现的。
看来我们需要自己实现From/To/By这几个属性了,这里只实现FromTo

这非常简单,我们很快就实现了两个属性:

        private double _from;

        
public double From

        
{

            
get return _from; }

            
set { _from = value; }

        }


        
private double _to;

        
public double To

        
{

            
get return _to; }

            
set { _to = value; }

        }

F5运行,通过了,但是动画没有动起来。

这是为什么?经过跟踪,很容易发现GetCurrentValueCore()ToFrom始终是0.0,想想也对,光定义了两个属性,这里,XAML在前台定义了一个QuadraticDoubleAnimation对象后,XAML中明明定义了这两个属性的值,为什么它们没有变化呢?。这很奇怪,因为在前台设置的Duration属性就返回到了后台。
通过查看TimeLine的元数据发现,Duration是通过DependencyProperty存在的,模仿一下:

代码改变如下: 

        public double From
        
{
            
get return (double)GetValue(FromProperty); }
            
set { SetValue(FromProperty, value); }
        }

        
public double To
        
{
            
get return (double)GetValue(ToProperty); }
            
set { SetValue(ToProperty, value); }
        }

 GetValueSetValue是通过ToPropertyFromProperty访问的,还需要注册两个属性名称:

 

        public static readonly DependencyProperty FromProperty =
    DependencyProperty.Register(
"From",
        
typeof(double),
        
typeof(QuadraticDoubleAnimation),
        
new PropertyMetadata(null));
 
        
public static readonly DependencyProperty ToProperty =
            DependencyProperty.Register(
"To",
                
typeof(double),
                
typeof(QuadraticDoubleAnimation),
                
new PropertyMetadata(null));

再运行,图像动起来了,速度先慢后快,按照我们的数学函数描述的方式运动,至此自定义动画成功
实际上程序中还有许多bug,比如当不指定From或者To的时候程序就会出错,From To应该为Double?类型~等,这里都省略了。。(先庆祝一下 呵呵)

最大的问题来了,为什么直接使用属性,不能达到效果,而使用DependencyProperty就可以了呢?这和XAML的工作方式有关系。
我们自定义的Animation对象在WPF中是一个Freezable(可冻结对象,假设这为对象A),默认情况下,它是没有被冻结的。也就是说它是可以被修改的,为了保证对它的修改不影响动画的正常进行,WPF会在开始动画的时候复制一个Animation对象(假设为对象B),对象B对编程者来说是不可知的,也就是说在动画进行的过程中,我们对Animation对象的任何操作实际上都是对对象A进行的操作。

下面我们来关注对象B的复制过程。对了,在这里:

        protected override Freezable CreateInstanceCore()
        {
            
return new QuadraticDoubleAnimation();
        }

Freezable需要被复制的时候,WPF就会调用这个方法来进行复制。所以,如果我们仅仅使用普通的属性定义方式,动画时候使用的QuadraticDoubleAnimation类实际上是一个刚刚初始化过的新的Animation类,当然没有包含FromTo的数据。

当使用了DependencyProperty的时候情况又怎么样呢?Freezable被复制时,CreateInstanceCore函数返回了一个空类,然后Freezable会将所有DependencyProperty中的值复制到新的类中。(我确定了Freezable确实会这么做,不过我还不清楚这种“自动深度复制”是Freezable的特性还是和DependencyProperty的特性有关,看起来似乎像前者,这里我没有深究,希望有人指教一下)

理解了这些,我们也可以尝试一些方法避开使用DependencyProperty(不过MSDN中极力推荐我们使用DependencyProperty,其实这样确实是一种更加良好的设计,下面还会看到有其他原因)

首先可以在CreateInstanceCore函数中手动的加入复制ToFrom属性的语句,像这样:

        protected override Freezable CreateInstanceCore()
        {
            QuadraticDoubleAnimation q 
= new QuadraticDoubleAnimation();
            q.From 
= this.From;
            q.To 
= 0; ;
           
return q;
        }


运行程序,一切正常。

还有更简单的方法,当一个Freezable已经被冻结的时候,再使用它就不回去得到一个副本了(这对效率的提升是非常大的),所以只需要在Window的初始化的代码中将我们定义的QuadraticDoubleAnimation冻结就行了(或者在XAML中加入PresentationOptions:Freeze="True" 这需要事先声明xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"

冻结之后,不使用DependecyProperty属性而只是用一般属性,运行程序,一切正常。

还没有完,在Freezable中我们了解到冻结之后属性不能再被改变,否则会抛出一个异常,我们来尝试一下。

加入一个按纽,Click事件处理函数如下:

        void btnClick(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(myAnimation.IsFrozen.ToString());
            myAnimation.To 
= 300;
        }

第一行确认myAnimation对象当前状态已经是被冻结了,第二行尝试改变它的值。

运行程序,点击按纽,弹出”True”,然后对象速度突然加快,To属性的更改生效了,这不是违反了Freezable的规则吗?

没错,这就是违反了Freezable的规则。这并不是WPFBug,而是我们事先已经违反了规则,Freezable的所有属性都需要定义成DependencyProperty,这样当改变属性是WPF才能进行判断,原来是我们先没有按规则出牌。

重新用DependencyProperty定义ToFrom两个属性,再运行程序,点按纽。

这下抛出了一个提示,To属性是只读属性。

所以我们还是按照规则使用DependencyProperty的好。

又有一个问题,刚才我们实现了一个挺酷的功能,在动画运行期实时改变了Animation的某个属性,这是在Animation没有冻结的情况下不大好办到的,不过我不推荐这样来改变属性,以下提供两种Animation没有被冻结且动画运行期改变属性的方法:

1 得到AnimationClock(使用CreatClock方法),得到当前时间,使动画停止,改变值,然后从新开始动画,定位到同样的时间,这实际上建立了一个新的动画的实例,性能上会有损耗。
2 CreatInstanceCore()方法中利用全局成员或者静态类或者其他什么方法,把创建的新的Animation的引用保存下来,需要的时候操作这个引用

涉及到的东西比较多比较杂,就懒得总结了~
问题讨论告一个段落,写了个小程序,了解了不少WPF的实现机制的东西。还是很好玩的 呵呵

posted on 2007-04-21 01:25  Yuxin Yang  阅读(3283)  评论(2编辑  收藏  举报