其他分享
首页 > 其他分享> > 从自定义DoubleAnimation开始

从自定义DoubleAnimation开始

作者:互联网

原文链接:http://www.cnblogs.com/yayx/archive/2007/04/21/721743.html 研究了好多天WPF了, 在cnblogs写第一篇技术文~纪念一下,大家也支持一下 呵呵
虽然是从从自定义DoubleAnimation开始,不过重点MS应该是在后面对Freezable DependencyProperty等的讨论~笔记的一部分,写的比较乱,不过思路还算清楚
下面尝试自定义一个DoubleAnimation
简单的准备,
一般的DoubleAnimation是线性的变换函数

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

 2.jpg

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

首先定义一个矩形

 

None.gif      <Rectangle Stroke="#FF000000" x:Name="myRectangle" Width="30" Height="30" HorizontalAlignment="Left" Margin="34,57.5,0,0" VerticalAlignment="Top">
None.gif
None.gif        <Rectangle.RenderTransform>
None.gif
None.gif            <TranslateTransform X="0" Y="0"/>
None.gif
None.gif        </Rectangle.RenderTransform>
None.gif
None.gif        <Rectangle.Fill>
None.gif
None.gif          <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
None.gif
None.gif            <GradientStop Color="#FF000000" Offset="0"/>
None.gif
None.gif            <GradientStop Color="#FFFFFFFF" Offset="1"/>
None.gif
None.gif          </LinearGradientBrush>
None.gif
None.gif        </Rectangle.Fill>
None.gif
None.gif      </Rectangle>
None.gif
None.gif

用DoubleAnimation让他动起来:

None.gif <Window.Triggers>
None.gif
None.gif    <EventTrigger RoutedEvent="Window.Loaded">
None.gif
None.gif      <BeginStoryboard>
None.gif
None.gif        <Storyboard>
None.gif
None.gif          <DoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="RenderTransform.X"
None.gif
None.gif                           Duration="0:0:3" From="0" To="180"/>
None.gif
None.gif        </Storyboard>
None.gif
None.gif      </BeginStoryboard>
None.gif
None.gif    </EventTrigger>
None.gif
None.gif </Window.Triggers>
None.gif

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

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

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

None.gif        protected override Freezable CreateInstanceCore()
ExpandedBlockStart.gif        {
InBlock.gif            return new QuadraticDoubleAnimation();
ExpandedBlockEnd.gif        }
None.gif

然后实现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属性中加入

None.gifxmlns:myAnimations="clr-namespace:myCustonAnimation"

Storyboard的代码:

None.gif        <Storyboard>
None.gif
None.gif          <myAnimations:QuadraticDoubleAnimation Storyboard.TargetName="myRectangle" Storyboard.TargetProperty="RenderTransform.X"
None.gif
None.gif                           Duration="0:0:3" From="0" To="180"/>
None.gif
None.gif        </Storyboard>
None.gif

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

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

None.gif        private double _from;
None.gif
None.gif        public double From
None.gif
ExpandedBlockStart.gif        {
InBlock.gif
ExpandedSubBlockStart.gif            get { return _from; }
InBlock.gif
ExpandedSubBlockStart.gif            set { _from = value; }
InBlock.gif
ExpandedBlockEnd.gif        }
None.gif
None.gif        private double _to;
None.gif
None.gif        public double To
None.gif
ExpandedBlockStart.gif        {
InBlock.gif
ExpandedSubBlockStart.gif            get { return _to; }
InBlock.gif
ExpandedSubBlockStart.gif            set { _to = value; }
InBlock.gif
ExpandedBlockEnd.gif        }
None.gif

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

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

代码改变如下: 

None.gif        public double From
ExpandedBlockStart.gifContractedBlock.gif        {
ExpandedSubBlockStart.gif            get { return (double)GetValue(FromProperty); }
ExpandedSubBlockStart.gif            set { SetValue(FromProperty, value); }
ExpandedBlockEnd.gif        }
None.gif        public double To
ExpandedBlockStart.gif        {
ExpandedSubBlockStart.gif            get { return (double)GetValue(ToProperty); }
ExpandedSubBlockStart.gif            set { SetValue(ToProperty, value); }
ExpandedBlockEnd.gif        }

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

 

        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类,当然没有包含From和To的数据。

当使用了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事件处理函数如下:

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

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

运行程序,点击按纽,弹出”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的实现机制的东西。还是很好玩的 呵呵

转载于:https://www.cnblogs.com/yayx/archive/2007/04/21/721743.html

标签:Freezable,自定义,开始,QuadraticDoubleAnimation,Animation,double,DoubleAnimation,Depen
来源: https://blog.csdn.net/weixin_30840573/article/details/95756632