WPF MVVM模式下动画的实现

在MVVM模式下,数据的显示都是通过绑定来实现的。当我们在ViewModel里修改数据时,View里面的界面会瞬间变化。但是如果我们希望这个变化有一个动画效果,应该怎么做呢?

可能一开始我们会想到DoubleAnimation、StoryBoard这些东西,但我们很快就会发现,它们只能操作View里面的元素,我们无法在ViewModel里使用它们。

我们在这里使用的方法是:创建一个类似DoubleAnimation的类,它的操作对象就是普通的double类型。在某一个时间段内,double类型的属性连续变化,View里的元素也会显示出一个动画的效果。

首先我们在View里放一个小正方形:

<Canvas>
    <Rectangle Width="10" Height="10" Fill="Red" Canvas.Left="{Binding RectLeft}"/>
</Canvas>

我们看到,正方形的Canvas.Left属性绑定了一个名为RectLeft的属性。而ViewModel也相当简单:

[ImplementPropertyChanged]
public class ViewModel
{
    public double RectLeft { get; set; }
}

我们这里用到了Fody.PropertyChanged,所以不用写RaisePropertyChanged那些代码。相关的资料可到百度上搜索。

好了,我们现在只要在某个时间段内,让RectLeft从一个值缓缓地变到另一个值,小正方形就会动起来了。

我们新建一个同样名叫DoubleAnimation的自定义类,然后我们这样来使用它:

DoubleAnimation ani = new DoubleAnimation(10, 700, 1000, (v) =>
{
    RectLeft = v;
});
ani.BeginAnimation();

第一个参数是From,第二个参数是To,第三个参数是时间,第四个参数是一个委托,用来改变RectLeft的值。由于RectLeft是一个属性,我们没办法用ref标记把它作为引用参数,只能用这种方法去改变它。

好了,我们现在就来看DoubleAnimation的实现,上代码:

public class DoubleAnimation
{
    /// <summary>
    /// 最小时间单元
    /// </summary>
    private const int MinTimeUnit = 40;

    private int Segments;
    private double[] MidValues;

    /// <summary>
    /// 构造DoubleAnimation
    /// </summary>
    /// <param name="from">起始点</param>
    /// <param name="to">结束点</param>
    /// <param name="interval">周期,单位毫秒</param>
    /// <param name="setvalue">设值函数</param>
    public DoubleAnimation(double from, double to, int interval, Action<double> setvalue)
    {
        From = from;
        To = to;
        Interval = interval;
        SetValue = setvalue;
    }

    /// <summary>
    /// 起始点
    /// </summary>
    private double From { get; set; }

    /// <summary>
    /// 结束点
    /// </summary>
    private double To { get; set; }

    /// <summary>
    /// 周期,单位毫秒
    /// </summary>
    private int Interval { get; set; }

    /// <summary>
    /// 设值函数
    /// </summary>
    private Action<double> SetValue;

    /// <summary>
    /// 完成函数
    /// </summary>
    public Action Completed;

    /// <summary>
    /// 缓动方法
    /// </summary>
    public EasingMethod EasingMethod { get; set; } = EasingMethod.Linear;

    /// <summary>
    /// 开始动画
    /// </summary>
    public void BeginAnimation()
    {
        Segments = Interval / MinTimeUnit;
        if (Segments == 0)
        {
            Segments = 1;
        }

        MidValues = new double[Segments + 1];
        MidValues[0] = From;
        MidValues[Segments] = To;
        CalcMidValues();

        new Thread(() =>
        {
            for (int i = 0; i < Segments; i++)
            {
                SetValue(MidValues[i]);
                Thread.Sleep(MinTimeUnit);
            }
            SetValue(MidValues[Segments]);

            Completed?.Invoke();
        }).Start();
    }

    /// <summary>
    /// 计算中间值
    /// </summary>
    private void CalcMidValues()
    {
        if (EasingMethod == EasingMethod.Linear)
        {
            double gap = (To - From) / Segments;
            double current = From;
            for (int i = 1; i < Segments; i++)
            {
                current += gap;
                MidValues[i] = current;
            }
        }
    }
}

代码其实很简单,我们分步骤看:

(1)我们设置好起点From、终点To,时间Interval。

(2)我们知道,1/24秒是一般电影的帧率,也就是差不多40ms一帧,在这个频率下,人眼会觉得动画是连续的。

(3)我们把总时间进行分段,例如是1s,这里面应该有1000/40=25段,端点就是26个。我们计算出26个端点的值。

(4)最普遍也最简单的计算就是线性插值了。我们知道,.NET里面的Animation提供了大量的EasingFunction,它们的作用就是在这里插值用的。这个日后可以完善,慢慢加到类里面。

(5)好了,最重要的一步,就是不断地改变RectLeft的值。在这里,我们是40ms改变一次。可以考虑使用定时器或是线程来做,我们这里用线程去做。代码确实简单,就是一个for循环,改变一次值之后,“睡”40ms。完了之后如果有需要的,可以调用一个Completed的事件。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值