UWP:使用Composition實現類似安卓的水波紋Ripple效果

来源:https://www.cnblogs.com/blue-fire/archive/2018/03/15/8575968.html
-Advertisement-
Play Games

使用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

受個人技術所限,沒有想到怎麼做到圓角或者不規則圖形,所以目前只支持直角矩形控制項


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • For 迴圈語句 基礎知識 for迴圈可以遍歷任何序列的項目,如一個列表或者一個字元串。 語法: for 迴圈規則: do sth 判斷對象是否可迭代 zip() 函數 函數用於將可迭代的對象作為參數,將對象中對應的元素打包成一個個元組,然後返回由這些元組組成的列表。 如果各個迭代器的元素個數不一致 ...
  • 1.一些簡單的dos命令: – d: 回車 盤符切換 – dir(directory):列出當前目錄下的文件以及文件夾 – del:刪除文件 – md:創建文件夾 – rd:刪除文件夾 – cd (change directory)改變指定目錄(進入指定目錄) 進入 cd 目錄;cd 多級目錄 回退 ...
  • 呵呵。大家都知道五服以內不得通婚,即兩個人最近的共同祖先如果在五代以內(即本人、父母、祖父母、曾祖父母、高祖父母)則不可通婚。本題就請你幫助一對有情人判斷一下,他們究竟是否可以成婚? 輸入格式: 輸入第一行給出一個正整數N(2 ≤ N ≤),隨後N行,每行按以下格式給出一個人的信息: 其中ID是5位 ...
  • 內容:顯示工具欄,設置主題、快捷鍵、開頭文字、解釋器 一些可能用到的設置,想起來就發上來。有需要的可以查看 ###############顯示工具欄 ##############設置主題,我用的主題是這種比較暗的,不喜歡太亮的,比如那種基本都是白色的。 ####################### ...
  • 目的: 為了從搜索結果中提取所有網頁,以備後續處理。 訪問百度鏈接分析 名稱 值 說明 wd 任意文字 關鍵字 rn 可以不指定,預設為10,最大為50,最小為1,可設置為任意值 一頁包含的結果條目數 pn 百度預設顯示760條,所以最後一頁為pn=750 第一條結果的索引位置 示例: https:... ...
  • 項目搭建 1.導入原型 只有前端頁面,但沒有後端代碼的項目。即一個只有頁面但沒有功能的項目! 導入原型: 2.前臺需求分析 前臺是針對會員購書,而後臺是管理員管理系統並完成售書。 2.1 前臺主頁 /index.jsp使用<jsp:forward>轉發到/jsps/main.jsp,、main.js ...
  • 運算符和條件語句 算術運算符 比較運算符 邏輯運算符 bool()判斷一個對象是真還是假 A and B if A ==true : return bool(B) if A == false : return false;先執行A ,如果A為true,則將B的結果返回。如果A為false,則直接返回 ...
  • 今天記錄一下Binding的基礎和具體的使用方法,說起這個Binding,在WPF中,Binding是很重要的特征,在傳統的Windows軟體來看,大多數都是UI驅動程式的模式,也可以說事件驅動程式,這個程式模式在工作過幾年的程式員中是根深蒂固的,WPF作為Winform的升級,它把UI驅動程式徹底 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...