首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >定制UIElement动画

定制UIElement动画
EN

Code Review用户
提问于 2011-04-01 21:30:39
回答 2查看 888关注 0票数 4

在WPF中拥有精美的动画非常好,所以我尝试为UIElements实现一个通用的淡入动画,这是我在应用程序中使用的弹出窗口。我在想,它是不是有什么问题,并考虑将它张贴在这里昨天。当时确实有很多错误,从那时起,我对代码做了很大的改进(我认为),但是现在可能仍然存在一些问题。

我发现的第一个缺陷是,通过暂时改变RenderTransform,如果启动相同的动画,而另一个动画还在播放,那么原版就会被搞砸,因为he的Completed事件将永远不会被调用,而原始的RenderTransform将与来自我被破坏的动画的半动画转换合并。

为了防止这一点,我现在有一个HashSet,它被检查为UIElement,如果另一个还在进行中,就不会启动动画,也不确定这是否是这里最好的方法。

代码语言:javascript
复制
namespace HB.Animation
{
    public enum Direction { Up, Down, Left, Right }
    public enum AnimationType { In, Out }

    public abstract class UIElementAnimationBase
    {
        private static readonly HashSet<UIElement> _animatedElements = new HashSet<UIElement>();

        private AnimationType _type = AnimationType.In;
        /// <summary>
        /// Gets or sets the type of the animation. Default is In.
        /// </summary>
        public AnimationType Type
        {
            get { return _type; }
            set { _type = value; }
        }

        private UIElement _target = null;
        /// <summary>
        /// Gets or sets the target of the animation.
        /// </summary>
        public UIElement Target
        {
            get { return _target; }
            set { _target = value; }
        }

        private TimeSpan _duration = TimeSpan.FromSeconds(0.3);
        /// <summary>
        /// Gets or sets the duration of the animation. Default is 0.3 seconds.
        /// </summary>
        public TimeSpan Duration
        {
            get { return _duration; }
            set { _duration = value; }
        }

        public event EventHandler Completed;

        protected void OnCompleted()
        {
            _animatedElements.Remove(Target);
            if (Completed != null)
            {
                Completed(this, null);
            }
        }

        public void BeginAnimation()
        {
            if (_animatedElements.Contains(Target))
            {
                return;
            }
            else
            {
                _animatedElements.Add(Target);
            }

            BeginAnimationDetail();
        }

        protected abstract void BeginAnimationDetail();
    }

    public class FadeAnimation : UIElementAnimationBase
    {
        private Direction _direction = Direction.Down;
        /// <summary>
        /// Gets or sets the direction of the animation. Default is Down.
        /// </summary>
        public Direction Direction
        {
            get { return _direction; }
            set { _direction = value; }
        }

        private double _distance = 50;
        /// <summary>
        /// Gets or sets the distance of the target travels in the animation. Default is 50.
        /// </summary>
        public double Distance
        {
            get { return _distance; }
            set { _distance = value; }
        }

        private FillBehavior _opacityBehavior = FillBehavior.HoldEnd;
        /// <summary>
        /// Gets or sets the fill behavior of the opacity sub-animation. Default is HoldEnd.
        /// </summary>
        public FillBehavior OpacityBehavior
        {
            get { return _opacityBehavior; }
            set { _opacityBehavior = value; }
        }

        public FadeAnimation(UIElement target)
        {
            Target = target;
        }

        public FadeAnimation(UIElement target, AnimationType type, Direction direction)
            : this(target)
        {
            Type = type;
            Direction = direction;
        }

        protected override void BeginAnimationDetail()
        {
            Transform tempTrans = Target.RenderTransform;

            TranslateTransform trans;
            EasingMode easing;
            double opacityTarget;
            double translateOrigin;
            double translateTarget;
            DependencyProperty translateProperty;

            switch (Type)
            {
                case AnimationType.In:
                    easing = EasingMode.EaseOut;
                    opacityTarget = 1;
                    translateTarget = 0;
                    switch (Direction)
                    {
                        case Direction.Up:
                            trans = new TranslateTransform(0, -Distance);
                            translateProperty = TranslateTransform.YProperty;
                            break;
                        case Direction.Down:
                            trans = new TranslateTransform(0, Distance);
                            translateProperty = TranslateTransform.YProperty;
                            break;
                        case Direction.Left:
                            trans = new TranslateTransform(-Distance, 0);
                            translateProperty = TranslateTransform.XProperty;
                            break;
                        case Direction.Right:
                            trans = new TranslateTransform(Distance, 0);
                            translateProperty = TranslateTransform.XProperty;
                            break;
                        default:
                            throw new InvalidOperationException();
                    }
                    break;
                case AnimationType.Out:
                    easing = EasingMode.EaseIn;
                    opacityTarget = 0;
                    trans = new TranslateTransform(0, 0);
                    switch (Direction)
                    {
                        case Direction.Up:
                            translateTarget = -Distance;
                            translateProperty = TranslateTransform.YProperty;
                            break;
                        case Direction.Down:
                            translateTarget = Distance;
                            translateProperty = TranslateTransform.YProperty;
                            break;
                        case Direction.Left:
                            translateTarget = -Distance;
                            translateProperty = TranslateTransform.XProperty;
                            break;
                        case Direction.Right:
                            translateTarget = Distance;
                            translateProperty = TranslateTransform.XProperty;
                            break;
                        default:
                            throw new InvalidOperationException();
                    }
                    break;
                default:
                    throw new InvalidOperationException();
            }

            TransformGroup group = new TransformGroup();
            if (tempTrans != null) group.Children.Add(tempTrans);
            group.Children.Add(trans);
            Target.RenderTransform = group;

            DoubleAnimation animTranslate = new DoubleAnimation(translateTarget, (Duration)Duration);
            animTranslate.EasingFunction = new CubicEase() { EasingMode = easing };
            DoubleAnimation animFade = new DoubleAnimation(opacityTarget, (Duration)Duration) { FillBehavior = OpacityBehavior };
            animTranslate.Completed += delegate
            {
                Target.RenderTransform = tempTrans;
                OnCompleted();
            };

            Target.BeginAnimation(UIElement.OpacityProperty, animFade);
            trans.BeginAnimation(translateProperty, animTranslate);
        }
    }
}

下面是一个用法示例:

代码语言:javascript
复制
private void DisplayMode_QuickSelect_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Popup popup = FindResource("DisplayModePopup") as Popup;
    FadeAnimation anim = new FadeAnimation(popup.Child) { Duration = Settings.BalloonAnimationDuration };
    if (popup.IsOpen)
    {
        anim.Type = HB.Animation.AnimationType.Out;
        anim.Completed += delegate { popup.IsOpen = false; };
    }
    else
    {
        popup.Child.Opacity = 0;
        popup.IsOpen = true;
    }
    anim.BeginAnimation();
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2011-04-01 22:50:37

我会专注于BeginAnimationDetail方法。

  • 可变的名字。我至少会重命名这些变量-- tempTranstranstempTrans名称没有告诉我这个变量是关于什么的。例如,我把它命名为originalTransform --这就足以理解为什么要将它添加到另一个转换中,然后再将控件的转换重置到这个转换中。从tempTrans的名字来看,还不清楚你将如何使用它。
  • 在该方法的开头,定义了几个变量,然后对~60行(两个switch语句)设置一些变量。除了代码的某些部分在代码中重复的事实之外,我发现在定义变量时,当变量被分配到远离行的位置时,很难读懂它们。为了避免这种情况,我会重写方法。算法可以是:
    • 计算inout点的位置。In = {0, 0}Out依赖于DirectionDistance
    • 决定在什么时候动画将开始,在什么时候它将完成。
    • 创建动画并启动它们。

结果(缩短约两倍):

代码语言:javascript
复制
   protected override void BeginAnimationDetail()
    {
        var inPoint = new Point(0, 0);
        Point outPoint;

        switch (Direction)
        {
            case Direction.Up:
                outPoint = new Point(0, -Distance);
                break;
            case Direction.Down:
                outPoint = new Point(0, Distance);
                break;
            case Direction.Left:
                outPoint = new Point(-Distance, 0);
                break;
            case Direction.Right:
                outPoint = new Point(Distance, 0);
                break;
            default:
                throw new InvalidOperationException();
        }

        Transform originalTransform = Target.RenderTransform;

        var easing = Type == AnimationType.In ? EasingMode.EaseOut : EasingMode.EaseIn;
        double opacityTarget = Type == AnimationType.In ? 1 : 0;
        Point from = Type == AnimationType.In ? outPoint : inPoint;
        Point to = Type == AnimationType.In ? inPoint : outPoint;

        var animatedTranslate = new TranslateTransform(from.X, from.Y);

        var group = new TransformGroup();
        if (originalTransform != null) group.Children.Add(originalTransform);
        group.Children.Add(animatedTranslate);
        Target.RenderTransform = group;

        var animFade = new DoubleAnimation(opacityTarget, Duration) {FillBehavior = OpacityBehavior};
        animFade.Completed += delegate
            {
                Target.RenderTransform = originalTransform;
                OnCompleted();
            };

        Target.BeginAnimation(UIElement.OpacityProperty, animFade);
        animatedTranslate.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(to.X, Duration) {EasingFunction = new CubicEase {EasingMode = easing}});
        animatedTranslate.BeginAnimation(TranslateTransform.YProperty, new DoubleAnimation(to.Y, Duration) {EasingFunction = new CubicEase {EasingMode = easing}});
    }
票数 5
EN

Code Review用户

发布于 2015-12-18 09:50:58

现在,使用自动实现的属性是可行的。

代码语言:javascript
复制
/// <summary> 
/// Gets or sets the type of the animation. Default is In.
/// </summary>
public AnimationType Type { get; set; } = AnimationType.In;

看上去比

私有AnimationType _type = AnimationType.In;/ /获取或设置动画的类型。默认值在。/公共AnimationType类型{ get {返回_type;} set { _type =值;}

使用C# 6 (VS 2015),我们可以简化OnCompleted()方法中的空检查,如下所示

代码语言:javascript
复制
protected void OnCompleted()
{
    _animatedElements.Remove(Target);

    Completed?.Invoke(this, null);
}  

如果我们利用HashSet.Add()方法的结果,我们可以将BeginAnimation()方法简化如下

代码语言:javascript
复制
public void BeginAnimation()
{
    if (_animatedElements.Add(Target))
    {
       BeginAnimationDetail();
    }
}  

但是,如果是NullReferenceException,我将抛出一个Target == null,因此它将从public方法而不是protected abstract void BeginAnimationDetail()方法中抛出。海事组织,这是必要的,因为Target属性是公共的,因此它可以被错误地设置为null

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/1617

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档