• UWP:使用Composition实现类似安卓的水波纹Ripple效果


    先放效果图:

    首先,建立一个RippleHelper.cs文件,然后建立以下附加属性:

    IsFillEnable:是否扩大到整个控件

    RippleDuration:持续时间

    RippleRadius:不扩大到整个控件时的最大半径

    RippleColor:波纹的颜色

            public static bool GetIsFillEnable(DependencyObject obj)
            {
                return (bool)obj.GetValue(IsFillEnableProperty);
            }
    
            public static void SetIsFillEnable(DependencyObject obj, bool value)
            {
                obj.SetValue(IsFillEnableProperty, value);
            }
    
            public static readonly DependencyProperty IsFillEnableProperty =
                DependencyProperty.RegisterAttached("IsFillEnable", typeof(bool), typeof(RippleHelper), new PropertyMetadata(false));
    
            public static TimeSpan GetRippleDuration(UIElement obj)
            {
                return (TimeSpan)obj.GetValue(RippleDurationProperty);
            }
    
            public static void SetRippleDuration(UIElement obj, TimeSpan value)
            {
                obj.SetValue(RippleDurationProperty, value);
            }
    
            public static readonly DependencyProperty RippleDurationProperty =
                DependencyProperty.RegisterAttached("RippleDuration", typeof(TimeSpan), typeof(RippleHelper), new PropertyMetadata(TimeSpan.FromMilliseconds(330)));
    
            public static double GetRippleRadius(UIElement obj)
            {
                return (double)obj.GetValue(RippleRadiusProperty);
            }
    
            public static void SetRippleRadius(UIElement obj, double value)
            {
                obj.SetValue(RippleRadiusProperty, value);
            }
    
            public static readonly DependencyProperty RippleRadiusProperty =
                DependencyProperty.RegisterAttached("RippleRadius", typeof(double), typeof(RippleHelper), new PropertyMetadata(100d));
    
            public static Color GetRippleColor(UIElement obj)
            {
                return (Color)obj.GetValue(RippleColorProperty);
            }
    
            public static void SetRippleColor(UIElement obj, Color value)
            {
                obj.SetValue(RippleColorProperty, value);
            }
    
            public static readonly DependencyProperty RippleColorProperty =
                DependencyProperty.RegisterAttached("RippleColor", typeof(Color), typeof(RippleHelper), new PropertyMetadata(Colors.White));
    

    接下来再写一个附加属性和一个enum

    public static RippleHelperState GetRippleHelperState(UIElement obj)
            {
                return (RippleHelperState)obj.GetValue(RippleHelperStateProperty);
            }
    
            public static void SetRippleHelperState(UIElement obj, RippleHelperState value)
            {
                obj.SetValue(RippleHelperStateProperty, value);
            }
    
            public static readonly DependencyProperty RippleHelperStateProperty =
                DependencyProperty.RegisterAttached("RippleHelperState", typeof(RippleHelperState), typeof(RippleHelper), new PropertyMetadata(RippleHelperState.None, (s, e) =>
                {
                    if (e.NewValue != null && e.OldValue != e.NewValue)
                    {
                        var value = (RippleHelperState)e.NewValue;
                        var oldvalue = (RippleHelperState)e.OldValue;
                        if (s is UIElement ele)
                        {
                            switch (value)
                            {
                                case RippleHelperState.Pressed:
                                    {
                                        ele.RemoveHandler(UIElement.PointerReleasedEvent, pointerEventHandler);
                                        ele.AddHandler(UIElement.PointerPressedEvent, pointerEventHandler, true);
                                    }
                                    break;
    
                                case RippleHelperState.Released:
                                    {
                                        ele.RemoveHandler(UIElement.PointerPressedEvent, pointerEventHandler);
                                        ele.AddHandler(UIElement.PointerReleasedEvent, pointerEventHandler, true);
                                    }
                                    break;
    
                                case RippleHelperState.None:
                                    {
                                        ele.RemoveHandler(UIElement.PointerPressedEvent, pointerEventHandler);
                                        ele.RemoveHandler(UIElement.PointerReleasedEvent, pointerEventHandler);
                                        ElementCompositionPreview.SetElementChildVisual(ele, null);
                                    }
                                    break;
                            }
                        }
                    }
                }));

    在命名空间里建立enum

        public enum RippleHelperState
        {
            Pressed, Released, None
        }

    然后编写两个鼠标事件,对应RippleHelperState的Pressed和Released两个状态

            private static void Ele_PointerPressed(object sender, PointerRoutedEventArgs e)
            {
                if (sender is UIElement ele)
                {
                    var position = e.GetCurrentPoint(ele).Position.ToVector2();
                    StartRippleAnimation(ele, position);
                }
            }
    
            private static void Ele_PointerReleased(object sender, PointerRoutedEventArgs e)
            {
                if (sender is UIElement ele)
                {
                    var position = e.GetCurrentPoint(ele).Position.ToVector2();
                    StartRippleAnimation(ele, position);
                }
            }
            public static void StartRippleAnimation(UIElement ele, Vector2 position)
            {
                StartRippleAnimation(ele, position, GetRippleColor(ele), GetIsFillEnable(ele), GetRippleDuration(ele), GetRippleRadius(ele));
            }
    
            public static void StartRippleAnimation(UIElement ele, Vector2 position, Color color, bool isFillEnable, TimeSpan duration, double radius = 0)
            {
                var hostVisual = ElementCompositionPreview.GetElementVisual(ele);
                var cVisual = ElementCompositionPreview.GetElementChildVisual(ele) as ContainerVisual;
                if (cVisual == null)
                {
                    cVisual = compositor.CreateContainerVisual();
                    SizeBind.ClearParameter("hostVisual");
                    SizeBind.SetReferenceParameter("hostVisual", hostVisual);
                    cVisual.StartAnimation("Size", SizeBind);
                    cVisual.Clip = compositor.CreateInsetClip();
                    ElementCompositionPreview.SetElementChildVisual(ele, cVisual);
                }
    
                var sVisual = CreateSpriteVisual(ele, color);
                cVisual.Children.InsertAtTop(sVisual);
                sVisual.Offset = new Vector3(position.X, position.Y, 0f);
    
                if (isFillEnable)
                {
                    var nWidth = Math.Max(Math.Max(position.X, ele.RenderSize.Width - position.X), Math.Max(position.Y, ele.RenderSize.Height - position.Y));
                    var r = Math.Sqrt(nWidth * nWidth * 2);
                    var finalScale = (float)r / 45f;
                    PropSet.InsertScalar("ScaleValue", finalScale);
                    ScaleAnimation.Duration = TimeSpan.FromMilliseconds(400);
                    OpacityAnimation.Duration = TimeSpan.FromMilliseconds(430);
                }
                else
                {
                    if (radius == 100d)
                    {
                        PropSet.InsertScalar("ScaleValue", 2f);
                    }
                    else
                    {
                        PropSet.InsertScalar("ScaleValue", (float)GetRippleRadius(ele) / 45f);
                    }
                }
    
                ScaleAnimation.Duration = duration;
                OpacityAnimation.Duration = duration;
    
                var batch = compositor.GetCommitBatch(CompositionBatchTypes.Animation);
                batch.Completed += (s1, e1) =>
                {
                    OnRippleComplated(ele);
                    cVisual.Children.Remove(sVisual);
                };
                sVisual.StartAnimationGroup(RippleAnimationGroup);
            }

     动画完成的事件:

            public static event EventHandler RippleComplated;
    
            private static void OnRippleComplated(UIElement ele)
            {
                RippleComplated?.Invoke(ele, EventArgs.Empty);
            }

    最后在类的开头编写Composition的动画和资源:

            private static readonly PointerEventHandler pointerEventHandler = new PointerEventHandler(Ele_PointerReleased);
            private static Compositor compositor => Window.Current.Compositor;
            private static ExpressionAnimation _SizeBind;
            private static CompositionEasingFunction _EaseOut;
            private static ScalarKeyFrameAnimation _OpacityAnimation;
            private static Vector3KeyFrameAnimation _ScaleAnimation;
            private static CompositionAnimationGroup _RippleAnimationGroup;
            private static CompositionPropertySet _PropSet;
            private static CompositionBrush _Mask;
    
            private static ExpressionAnimation SizeBind
            {
                get
                {
                    if (_SizeBind == null) _SizeBind = compositor.CreateExpressionAnimation("hostVisual.Size");
                    return _SizeBind;
                }
            }
    
            private static CompositionEasingFunction EaseOut
            {
                get
                {
                    if (_EaseOut == null) _EaseOut = compositor.CreateCubicBezierEasingFunction(new Vector2(0f, 0f), new Vector2(0.9f, 1f));
                    return _EaseOut;
                }
            }
    
            private static ScalarKeyFrameAnimation OpacityAnimation
            {
                get
                {
                    if (_OpacityAnimation == null)
                    {
                        _OpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
                        _OpacityAnimation.InsertKeyFrame(0f, 1f, EaseOut);
                        _OpacityAnimation.InsertKeyFrame(1f, 0f, EaseOut);
                        _OpacityAnimation.Duration = TimeSpan.FromMilliseconds(350);
                        _OpacityAnimation.Target = "Opacity";
                    }
                    return _OpacityAnimation;
                }
            }
    
            private static Vector3KeyFrameAnimation ScaleAnimation
            {
                get
                {
                    if (_ScaleAnimation == null)
                    {
                        _ScaleAnimation = compositor.CreateVector3KeyFrameAnimation();
                        _ScaleAnimation.InsertKeyFrame(0f, new Vector3(0f, 0f, 1f), EaseOut);
                        _ScaleAnimation.InsertExpressionKeyFrame(0.8f, "Vector3(propSet.ScaleValue,propSet.ScaleValue,1f)", EaseOut);
                        _ScaleAnimation.InsertExpressionKeyFrame(1f, "Vector3(propSet.ScaleValue,propSet.ScaleValue,1f)", EaseOut);
                        _ScaleAnimation.SetReferenceParameter("propSet", PropSet);
                        _ScaleAnimation.Duration = TimeSpan.FromMilliseconds(320);
                        _ScaleAnimation.Target = "Scale";
                    }
                    return _ScaleAnimation;
                }
            }
    
            private static CompositionAnimationGroup RippleAnimationGroup
            {
                get
                {
                    if (_RippleAnimationGroup == null)
                    {
                        _RippleAnimationGroup = compositor.CreateAnimationGroup();
                        _RippleAnimationGroup.Add(OpacityAnimation);
                        _RippleAnimationGroup.Add(ScaleAnimation);
                    }
                    return _RippleAnimationGroup;
                }
            }
    
            private static CompositionPropertySet PropSet
            {
                get
                {
                    if (_PropSet == null)
                    {
                        _PropSet = compositor.CreatePropertySet();
                        PropSet.InsertScalar("ScaleValue", 2f);
                    }
                    return _PropSet;
                }
            }
    
            private static CompositionBrush Mask
            {
                get
                {
                    if (_Mask == null)
                    {
                        var surface = LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///MaterialLibs/Assets/RippleMask.png"), new Windows.Foundation.Size(100d, 100d));
                        _Mask = compositor.CreateSurfaceBrush(surface);
                    }
                    return _Mask;
                }
            }

     最后在Mask读取的Uri的对应位置放上如下的图片文件:

    完整代码已经开源在Github:https://github.com/cnbluefire/MaterialLibs

    受个人技术所限,没有想到怎么做到圆角或者不规则图形,所以目前只支持直角矩形控件

  • 相关阅读:
    微人事项目-mybatis-持久层
    通过外键连接多个表
    springioc
    Redis 消息中间件 ServiceStack.Redis 轻量级
    深度数据对接 链接服务器 数据传输
    sqlserver 抓取所有执行语句 SQL语句分析 死锁 抓取
    sqlserver 索引优化 CPU占用过高 执行分析 服务器检查
    sql server 远程备份 bak 删除
    冒泡排序
    多线程 异步 beginInvoke EndInvoke 使用
  • 原文地址:https://www.cnblogs.com/blue-fire/p/8575968.html
Copyright © 2020-2023  润新知