如何在UWP中创建自定义动画

时间:2017-09-20 08:28:32

标签: c# uwp

UWP中的基本动画很好,除了我想创建自己的动画。所以我查看了不同的动画,发现它们都是Timeline的子类。为了测试,我决定像这样做一个DoubleAnimation类的副本:

public sealed class MyAnimation : Timeline
{
    public static DependencyProperty _ByProperty;
    public static DependencyProperty _EasingFunctionProperty;
    public static DependencyProperty _EnableDependentAnimationProperty;
    public static DependencyProperty _FromProperty;
    public static DependencyProperty _ToProperty;

    public MyAnimation() : base()
    {
    }

    static MyAnimation()
    {
        _ByProperty = DependencyProperty.Register("By", typeof(double?), typeof(MyAnimation), new PropertyMetadata((double?)null));
        _EasingFunctionProperty = DependencyProperty.Register("EasingFunction", typeof(EasingFunctionBase), typeof(MyAnimation), new PropertyMetadata(null));
        _EnableDependentAnimationProperty = DependencyProperty.Register("EnableDependentAnimation", typeof(bool), typeof(MyAnimation), new PropertyMetadata(false));
        _FromProperty = DependencyProperty.Register("From", typeof(double?), typeof(MyAnimation), new PropertyMetadata((double?)null));
        _ToProperty = DependencyProperty.Register("To", typeof(double?), typeof(MyAnimation), new PropertyMetadata((double?)null));
    }

    public static DependencyProperty ByProperty { get { return _ByProperty; } }
    public static DependencyProperty EasingFunctionProperty { get { return _EasingFunctionProperty; } }
    public static DependencyProperty EnableDependentAnimationProperty { get { return _EnableDependentAnimationProperty; } }
    public static DependencyProperty FromProperty { get { return _FromProperty; } }
    public static DependencyProperty ToProperty { get { return _ToProperty; } }

    public double? To { get { return (double?)GetValue(_ToProperty); } set { SetValue(_ToProperty, value); } }
    public double? From { get { return (double?)GetValue(_FromProperty); } set { SetValue(_FromProperty, value); } }
    public bool EnableDependentAnimation { get { return (bool)GetValue(_EnableDependentAnimationProperty); } set { SetValue(_EnableDependentAnimationProperty, value); } }
    public EasingFunctionBase EasingFunction { get { return (EasingFunctionBase)GetValue(_EasingFunctionProperty); } set { SetValue(_EasingFunctionProperty, value); } }
    public double? By { get { return (double?)GetValue(_ByProperty); } set { SetValue(_ByProperty, value); } }
}

然后我创建了一个要移动的对象:

<Grid Name="Root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Ellipse Width="200" Height="200" Fill="Green" Name="MyEllipse"/>
</Grid>

然后我在加载所有内容时启动动画:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    Storyboard sb = new Storyboard();
    MyAnimation da = new MyAnimation();
    da.From = 200;
    da.To = 500;
    da.Duration = new Duration(TimeSpan.FromSeconds(5));
    da.EnableDependentAnimation = true;
    sb.Children.Add(da);
    Storyboard.SetTargetProperty(da, "Width");
    Storyboard.SetTarget(da, MyEllipse);
    sb.Begin();
}

现在我的问题。当我运行这个时,我得到以下异常:

ERROR: System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
    at Windows.UI.Xaml.Media.Animation.Storyboard.Begin()
    at TestAnimation.MainPage.Button_Click(Object sender, RoutedEventArgs e)

对于出了什么问题,我没有给出任何解释。我猜测在构造函数中需要做更多的事情,但是我无法读取DoubleAnimation的源代码,只读取元数据,这使得无法知道实际发生了什么。任何人都知道需要做什么才能让它发挥作用?

2 个答案:

答案 0 :(得分:0)

请阅读有关该问题的评论,因为我认为它们仍然可以更好地回答该问题

但是,我遇到了这个问题,因为我也得到了

ERROR: System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component

就我而言,它与动画系统无关。这是因为我在缩放算法上犯了一个错误,并且从我的new Size(27688, 8)返回了所需的override MeasureOverride(...)

答案 1 :(得分:0)

根据我的阅读,并且经过多次尝试,UWP无法提供创建自己的动画的方法,因为没有方法可以从时间轴中进行覆盖。

我的场景应该是一个简单的场景-如何使用DoubleAnimation为网格设置动画?听起来微不足道,但a)Grid占用GridLength而不是Double,b)无法将IValueConverter附加到StoryBoard.TargetProperty,并且c)UWP似乎不支持创建自己的自定义动画。

因此,剩下的唯一选择就是利用现有动画并在其上构建一个层。最常见的方法是在页面中使用两个依赖项属性,将其中一个与DoubleAnimation绑定,将另一个与Grid绑定。可行,但它不可扩展,尤其是当您要分离样式时。

这是我的方法概述:  -创建将属性“ Converter”附加到任何依赖项对象的全局ConverterService  -创建一个作为IValueConverter的通用转换器  -通用值转换器具有一个名为Tin的Input的DependencyProperty和另一个名为Tout类型的Output的DependencyProperty  -将DoubleAnimation与转换器的输入绑定,并将输出与网格绑定。

使用方法如下:

    <Grid Style="{StaticResource MainGrid}" x:Name="MainGrid">

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding ElementName=MyMove, Path=Output}" x:Name="MyColumn">
            <conv:ConverterService.Converter>
                <conv:DoubleToGridLengthConverter x:Name="MyMove" Input="2" GridUnit="Star" />
            </conv:ConverterService.Converter>
        </ColumnDefinition>
     </Grid.ColumnDefinitions>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="Portrait">
                <VisualState.StateTriggers>
                    <stateTriggers:WindowAspectRatioTrigger Orientation="Portrait" />
                </VisualState.StateTriggers>
                <VisualState.Storyboard>
                    <Storyboard>

                        <DoubleAnimation
                            BeginTime="0:0:0"
                            EnableDependentAnimation="True"
                            Storyboard.TargetName ="MyMove"
                            Storyboard.TargetProperty="Input"
                            From="1" To="15" Duration="0:0:3" FillBehavior="HoldEnd" />
                    </Storyboard>
                </VisualState.Storyboard>
            </VisualState>


        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

   </Grid>

当然,请记住在页面顶部导入名称空间。 就我而言:

<base:BasePage
x:Class="MyProject.MainPage"
x:Name="MyPage"
xmlns:base ="using:MyProject.Base"
xmlns:local="using:MyProject"
xmlns:conv="using:MyProject.Converters"
xmlns:stateTriggers="using:MyProject.StateTriggers"
xmlns:ctrl="using:MyProject.UserControls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

这是ConverterService和BaseConverter和DoubleToGridLengthConverter的代码:

   /// <summary>A generic converter.</summary>
public interface IConverter: IValueConverter    
{
    /// <summary>The input value.</summary>
    object Input { get; set; }

    /// <summary>The output value.</summary>
    object Output { get; set; }
}

/// <summary>A service that provides conversion capabilities to dependency objects via an attached property.</summary>
public abstract class ConverterService : DependencyObject
{
    /// <summary>The Converter dependency property.</summary>
    public static readonly DependencyProperty ConverterProperty;

    /// <summary>The Converters dependency property which is a collection of Converter.</summary>
    public static readonly DependencyProperty ConvertersProperty;

    static ConverterService()
    {
        ConverterProperty = DependencyProperty.RegisterAttached("Converter",
                                                                typeof(IConverter),
                                                                typeof(ConverterService),
                                                                new PropertyMetadata(null));

        ConvertersProperty = DependencyProperty.RegisterAttached("Converters",
                                                                typeof(IList<IConverter>),
                                                                typeof(ConverterService),
                                                                new PropertyMetadata(new List<IConverter>()));
    }

    /// <summary>Property getter for attached property Converter.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <returns>Returns the converter associated with the specified dependency object.</returns>
    public static IConverter GetConverter(DependencyObject element)
    {
        return (IConverter)element.GetValue(ConverterProperty);
    }

    /// <summary>Property getter for attached property Converter.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <param name="value">The converter to associate.</param>
    public static void SetConverter(DependencyObject element, IConverter value)
    {
        element.SetValue(ConverterProperty, value);
    }

    /// <summary>Property getter for attached property Converters.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <returns>Returns the collection of converters associated with the specified dependency object.</returns>
    public static IList<IConverter> GetConverters(DependencyObject element)
    {
        return (IList<IConverter>)element.GetValue(ConverterProperty);
    }

    /// <summary>Property getter for attached property Converters.</summary>
    /// <param name="element">The dependency object to which the attached property applies.</param>
    /// <param name="value">The converters to associate.</param>
    public static void SetConverters(DependencyObject element, IList<IConverter> value)
    {
        element.SetValue(ConverterProperty, value);
    }
}

/// <summary>A strongly-typed base converter.</summary>
/// <typeparam name="Tin">The input type.</typeparam>
/// <typeparam name="Tout">The output type.</typeparam>
public abstract class BaseConverter<Tin, Tout>: DependencyObject, IConverter
{
    /// <summary>The Input dependency property.</summary>
    public static readonly DependencyProperty InputProperty;

    /// <summary>The Output dependency property.</summary>
    public static readonly DependencyProperty OutputProperty;

    static BaseConverter()
    {
        OutputProperty = DependencyProperty.Register("Output",
                                                     typeof(Tout),
                                                     typeof(BaseConverter<Tin, Tout>),
                                                     new PropertyMetadata(GetDefault(typeof(Tout)), OutChanged));

        InputProperty = DependencyProperty.Register("Input",
                                                     typeof(Tin),
                                                     typeof(BaseConverter<Tin, Tout>),
                                                     new PropertyMetadata(GetDefault(typeof(Tin)), InChanged));
    }

    /// <summary>Gets or sets the input value to convert from.</summary>
    public Tin Input
    {
        get { return (Tin)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }

    /// <summary>Gets or sets the output value to convert to.</summary>
    public Tout Output
    {
        get { return (Tout)GetValue(OutputProperty); }
        set { SetValue(OutputProperty, value); }
    }

    /// <summary>Gets the from type.</summary>
    public static Type From
    {
        get { return typeof(Tin); }
    }        

    /// <summary>Gets the to type.</summary>
    public static Type To
    {
        get { return typeof(Tout); }
    }

    #region IConverter

    object IConverter.Input { get => Input; set => Input = (Tin)value; }
    object IConverter.Output { get => Output; set => Output = (Tout)value; }

    #endregion

    #region IValueConverter

    object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
    {
        return Convert((Tin)value, parameter, language);
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return ConvertBack((Tout)value, parameter, language);
    }

    #endregion

    /// <summary>Converts an input value into an output value.</summary>
    /// <param name="value">The value to convert.</param>
    /// <param name="parameter">An optional parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <param name="language">An optional language parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <returns>Returns the converted value.</returns>
    protected abstract Tout Convert(Tin value, object parameter, string language);

    /// <summary>Converts back an output value into its original input.</summary>
    /// <param name="value">The value to convert.</param>
    /// <param name="parameter">An optional parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <param name="language">An optional language parameter to pass onto the conversion process (from IValueConverter).</param>
    /// <returns>Returns the converted value from output to input.</returns>
    protected abstract Tin ConvertBack(Tout value, object parameter, string language);

    private static object GetDefault(Type type)
    {
        return type.IsValueType ? Activator.CreateInstance(type) : null;
    }

    private static void InChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as BaseConverter<Tin, Tout>;
        control.Output = (Tout)((d as IValueConverter).Convert(e.NewValue, typeof(Tout), null, null));
    }

    private static void OutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as BaseConverter<Tin, Tout>;
        //control.Input = (Tin)((d as IValueConverter).ConvertBack(e.NewValue, typeof(Tin), null, null));
    }        
}

    /// <summary>Converts Double to and from GridLength.</summary>
public sealed class DoubleToGridLengthConverter : BaseConverter<double?, GridLength?>
{
    /// <summary>The GridUnit dependency property.</summary>
    public static readonly DependencyProperty GridUnitProperty;

    static DoubleToGridLengthConverter()
    {
        GridUnitProperty = DependencyProperty.Register("GridUnit",
                                                       typeof(GridUnitType),
                                                       typeof(DoubleToGridLengthConverter),
                                                       new PropertyMetadata(GridUnitType.Auto, UnitChanged));
    }

    /// <summary>Gets or sets the type of grid unit to be used in the conversions.</summary>
    public GridUnitType GridUnit
    {
        get { return (GridUnitType)GetValue(GridUnitProperty); }
        set { SetValue(GridUnitProperty, value); }
    }

    protected override GridLength? Convert(double? value, object parameter, string language)
    {
        return value == null || !value.HasValue
               ? new GridLength()
               : new GridLength(value.Value, this.GridUnit);
    }

    protected override double? ConvertBack(GridLength? value, object parameter, string language)
    {
        return value == null || !value.HasValue
               ? default(double?)
               : value.Value.Value;
    }

    private static void UnitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as DoubleToGridLengthConverter;
        control.Output = control.Convert(control.Input, null, null);
    }
}

如果要使用我的AspectRatio状态触发器,则为:

    /// <summary>Aspect ratios.</summary>
public enum AspectRatio
{
    /// <summary>Portrait.</summary>
    Portrait,

    /// <summary>Landscape.</summary>
    Landscape
}

/// <summary>A state trigger based on the aspect ratio of the window.</summary>
public class WindowAspectRatioTrigger: StateTriggerBase
{
    /// <summary>The target orientation.</summary>
    private static readonly DependencyProperty OrientationProperty;

    static WindowAspectRatioTrigger()
    {
        OrientationProperty = DependencyProperty.Register("Orientation",
                                                          typeof(AspectRatio),
                                                          typeof(WindowAspectRatioTrigger),
                                                          new PropertyMetadata(AspectRatio.Landscape, new PropertyChangedCallback(DesiredAspectRatioChanged)));
    }

    public WindowAspectRatioTrigger()
    {
        this.OnAspectRatioChanged(this.Orientation);
        Window.Current.SizeChanged += Current_SizeChanged;
    }

    /// <summary>Gets or sets the desired aspect ratio for the trigger.</summary>
    public AspectRatio Orientation
    {
        get { return (AspectRatio)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }

    private async void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            SetActive(IsActive(e.Size, this.Orientation));
        });
    }

    private static void DesiredAspectRatioChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as WindowAspectRatioTrigger;
        control.OnAspectRatioChanged((AspectRatio)e.NewValue);
    }

    private static bool IsActive(Size windowSize, AspectRatio aspectRatio)
    {
        var currentOrientation = windowSize.Width >= windowSize.Height
                                 ? AspectRatio.Landscape
                                 : AspectRatio.Portrait;

        return aspectRatio == currentOrientation;
    }

    private async void OnAspectRatioChanged(AspectRatio aspectRatio)
    {
        var dimensions = Window.Current.Bounds;
        var size = new Size(dimensions.Width, dimensions.Height);

        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            SetActive(IsActive(size, aspectRatio));
        });
    }
}