从自定义DoubleAnimation说起(WPF)

首发:http://www.cnblogs.com/yayx/archive/2007/04/21/721743.html

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

下面尝试自定义一个


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

 

可能某些时候需要复杂一点的变化,比如用二次曲线吧(其实这个可以用其他方法很简单的实现,这里只是讨论自定义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让他动起来:Animation。WPF提供了DoubleAnimationBase类,用于扩展。

  < 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 >

下面开始自定义一个

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

首先来实现CreateInstanceCore() 这里暂时返回一个本类的新的实例就行了GetCurrentValueCore(),前面提到过TimeClock会通过GetCurrentValue()方法获得当前的值,不过GetCurrentValue()方法除了完成计算值之外还会完成其他工作,因此不能够直接定义GetCurrentValue()方法,定义GetCurrentValueCore(),就行了.

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

然后实现

我们首先计算整个delta值,也就是动画全程的距离,再通过y=x*x*delta计算并返回当前,很简单的两个语句,一切看起来都不错,应该可以运行了。在XAML添加应用,并把对DoubleAnimation的引用修改为对我们的QuadraticDoubleAnimation的引用。

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

完成了这些

在Window属性中加入Storyboard的代码:

xmlns:myAnimations="clr-namespace:myCustonAnimation"

 

         < Storyboard >

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

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

        
</ Storyboard >

 

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

这非常简单,我们很快就实现了两个属性:运行,通过了,但是动画没有动起来。

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

代码改变如下:  GetValue和SetValue是通过ToProperty和FromProperty访问的,还需要注册两个属性名称:

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

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

 

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

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

         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 ));

 

下面我们来关注对象B的复制过程。对了,在这里:当Freezable需要被复制的时候,WPF就会调用这个方法来进行复制。所以,如果我们仅仅使用普通的属性定义方式,动画时候使用的QuadraticDoubleAnimation类实际上是一个刚刚初始化过的新的Animation类,当然没有包含From和To的数据。

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

 

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

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

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

         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事件处理函数如下:第一行确认myAnimation对象当前状态已经是被冻结了,第二行尝试改变它的值。

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

 

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

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

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

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

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

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

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

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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值