UWP:使用Behavior實現Button點擊動態效果

来源:http://www.cnblogs.com/blue-fire/archive/2017/07/25/7237158.html
-Advertisement-
Play Games

廢話不多說,先上效果 沒有做成安卓那種圓形的原因是...人家真的不會嘛... 好了下麵是正文: 首先在工程中引入Behavior的庫,我們使用Nuget。 在項目->引用上點擊右鍵,點擊管理Nuget程式包,然後瀏覽里搜索Microsoft.Xaml.Behaviors.Uwp.Managed 或者 ...


廢話不多說,先上效果

沒有做成安卓那種圓形的原因是...人家真的不會嘛...

好了下麵是正文:

首先在工程中引入Behavior的庫,我們使用Nuget。

在項目->引用上點擊右鍵,點擊管理Nuget程式包,然後瀏覽里搜索Microsoft.Xaml.Behaviors.Uwp.Managed

或者在程式包管理控制台里(如果輸出右邊沒有這個標簽,使用工具->Nuget包管理器->程式包管理控制台打開),輸入命令

Install-Package Microsoft.Xaml.Behaviors.Uwp.Managed

回車,坐等,引入成功。

然後我們新建一個類,名字叫ButtonBehavior,繼承IBehavior介面,並且實現Attach和Detach方法(不用傻傻的敲,自動補全就可以)。

這時文檔的結構是這樣的:

namespace MyBehavior
{
    public class Base : DependencyObject, IBehavior
    {
        public DependencyObject AssociatedObject { get; set; }
        public void Attach(DependencyObject associatedObject)
        {
            AssociatedObject  = associatedObject;
            //這裡寫代碼
        }
        public void Detach()
        {

        }
    }
}

給控制項設置Behavior時,程式會通過Attach方法,將控制項傳到我們的類里,也就是associatedObject。

接著,當然是使用Composition了。。。我又不會別的。

先聲明一堆準備用的對象:

double SizeValue;
double ScaleValue;

Compositor compositor;

Visual hostVisual;
ContainerVisual containerVisual;
SpriteVisual rectVisual;

ScalarKeyFrameAnimation PressSizeAnimation;
ScalarKeyFrameAnimation PressOffsetAnimation;
ScalarKeyFrameAnimation PressOpacityAnimation;
CompositionAnimationGroup PressAnimationGroup;

ScalarKeyFrameAnimation ReleaseSizeAnimation;
ScalarKeyFrameAnimation ReleaseOffsetAnimation;
ScalarKeyFrameAnimation ReleaseOpacityAnimation;
CompositionAnimationGroup ReleaseAnimationGroup;

然後該處理一下可愛的AssociatedObject了:

public virtual void Attach(DependencyObject associatedObject)
{
    AssociatedObject = associatedObject;
    if (AssociatedObject is FrameworkElement element)
    {
        if (element.ActualWidth > 0 && element.ActualHeight > 0)
            Init();
        else element.Loaded += Element_Loaded;

        hostVisual = ElementCompositionPreview.GetElementVisual(element);
        compositor = hostVisual.Compositor;
        element.AddHandler(UIElement.PointerPressedEvent, new PointerEventHandler(Element_PointerPressed), true);
        element.AddHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(Element_PointerReleased), true);
    }
    else return;
}

這裡掛上Loaded事件是因為,如果控制項沒有載入完成之前設置了Behavior,我們在Attach里獲取到的數據就不全了。

然後是Init方法,這是整個Behavior的核心:

void Init()
{
    if (AssociatedObject is FrameworkElement element)
    {
        hostVisual = ElementCompositionPreview.GetElementVisual(element); //獲取控制項Visual
        compositor = hostVisual.Compositor;  //獲取Compositor,Composition的大多數對象都需要他來創建

        var temp = ElementCompositionPreview.GetElementChildVisual(element);
        if (temp is ContainerVisual tempContainerVisual) containerVisual = tempContainerVisual;
        else
        {
            containerVisual = compositor.CreateContainerVisual();  //創建ContainerVisual
            ElementCompositionPreview.SetElementChildVisual(element, containerVisual);  //把ContainerVisual設置成控制項的子Visual
        }

    }
}

這裡有個小坑,ElementCompositionPreview類里,只有SetElementChildVisual方法,卻並沒有RemoveChildVisual的方法。所以我們給按鈕插入一個子ContainerVisual,ContainerVisual可以所謂容器盛放其他Visual,並且,可以移除。如果不這麼做,移除Behavior的時候會爆錯。

然後寫動畫,動畫分為兩部分,分別是按下和釋放。我的思路是這樣,滑鼠按下時,獲取到起始坐標,把讓特效Visual移動到起始橫坐標的位置,然後讓特效Visual的寬度從0到和控制項寬度一樣大,與此同時,特效Visual從起始位置((0,0)的右邊)慢慢向左移動,這樣就能製作出一個向外擴散的效果。

思路有了,繼續寫Init方法:

void Init()
{
    if (AssociatedObject is FrameworkElement element)
    {
        hostVisual = ElementCompositionPreview.GetElementVisual(element); //獲取控制項Visual
        compositor = hostVisual.Compositor;  //獲取Compositor,Composition的大多數對象都需要他來創建

        var temp = ElementCompositionPreview.GetElementChildVisual(element);
        if (temp is ContainerVisual tempContainerVisual) containerVisual = tempContainerVisual;
        else
        {
            containerVisual = compositor.CreateContainerVisual();  //創建ContainerVisual
            ElementCompositionPreview.SetElementChildVisual(element, containerVisual);  //把ContainerVisual設置成控制項的子Visual
        }

        rectVisual = compositor.CreateSpriteVisual();  //創建我們的正主,特效Visual

        var bindSizeAnimation = compositor.CreateExpressionAnimation("hostVisual.Size.Y");
        bindSizeAnimation.SetReferenceParameter("hostVisual", hostVisual);
        rectVisual.StartAnimation("Size.Y", bindSizeAnimation);
        //創建一個表達式動畫,把我們自己創建的特效Visual的高度和控制項Visual的高度綁定到一起

        rectVisual.Brush = compositor.CreateColorBrush(Windows.UI.Colors.Black);  //設置特效Visual的筆刷
        rectVisual.Opacity = 0f;  //設置特效Visual的初始透明度

        containerVisual.Children.InsertAtTop(rectVisual);  把特效Visual插入到ContainerVisual的頂部
        var easeIn = compositor.CreateCubicBezierEasingFunction(new Vector2(0.5f, 0.0f), new Vector2(1.0f, 1.0f));
        //創建一個關鍵幀動畫用到的貝塞爾曲線

        PressSizeAnimation = compositor.CreateScalarKeyFrameAnimation();
        PressSizeAnimation.InsertKeyFrame(0f, 0f, easeIn);
        PressSizeAnimation.InsertExpressionKeyFrame(1f, "hostVisual.Size.X", easeIn);
        PressSizeAnimation.SetReferenceParameter("hostVisual", hostVisual);
        PressSizeAnimation.Duration = TimeSpan.FromSeconds(1);
        PressSizeAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;  //動畫中途暫停時,將動畫的當前值設定到對象上
        PressSizeAnimation.Target = "Size.X";
        //創建按下後,特效Visual的寬度的關鍵幀動畫,持續1秒

        PressOffsetAnimation = compositor.CreateScalarKeyFrameAnimation();
        PressOffsetAnimation.InsertExpressionKeyFrame(0f, "This.CurrentValue", easeIn);
        PressOffsetAnimation.InsertKeyFrame(1f, 0f, easeIn);
        PressOffsetAnimation.Duration = TimeSpan.FromSeconds(1);
        PressOffsetAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
        PressOffsetAnimation.Target = "Offset.X";
        //創建按下後,特效Visual的橫向偏移的關鍵幀動畫,持續1秒

        PressOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
        PressOpacityAnimation.InsertKeyFrame(0f, 0.3f, easeIn);
        PressOpacityAnimation.InsertKeyFrame(1f, 0.5f, easeIn);
        PressOpacityAnimation.Duration = TimeSpan.FromSeconds(1);
        PressOpacityAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
        PressOpacityAnimation.Target = "Opacity";
        //創建按下後,特效Visual的透明度的關鍵幀動畫,持續1秒


        PressAnimationGroup = compositor.CreateAnimationGroup();
        PressAnimationGroup.Add(PressSizeAnimation);
        PressAnimationGroup.Add(PressOffsetAnimation);
        PressAnimationGroup.Add(PressOpacityAnimation);
        //創建一個動畫組,把上面三個動畫放在一起,類似Storyboard


        ReleaseSizeAnimation = compositor.CreateScalarKeyFrameAnimation();
        ReleaseSizeAnimation.InsertExpressionKeyFrame(0f, "This.CurrentValue", easeIn);

        //This.CurrentValue是表達式動畫中的一個特殊用法,可以將設置的屬性的當前值傳遞給動畫

        ReleaseSizeAnimation.InsertExpressionKeyFrame(1f, "hostVisual.Size.X", easeIn);
        ReleaseSizeAnimation.SetReferenceParameter("hostVisual", hostVisual);
        ReleaseSizeAnimation.Duration = TimeSpan.FromSeconds(0.2);
        ReleaseSizeAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
        ReleaseSizeAnimation.Target = "Size.X";
        //創建釋放後,特效Visual的寬度的關鍵幀動畫,持續0.2秒。

        ReleaseOffsetAnimation = compositor.CreateScalarKeyFrameAnimation();
        ReleaseOffsetAnimation.InsertExpressionKeyFrame(0f, "This.CurrentValue", easeIn);
        ReleaseOffsetAnimation.InsertKeyFrame(1f, 0f, easeIn);
        ReleaseOffsetAnimation.Duration = TimeSpan.FromSeconds(0.2);
        ReleaseOffsetAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
        ReleaseOffsetAnimation.Target = "Offset.X";
        //創建釋放後,特效Visual的橫向偏移的關鍵幀動畫,持續0.2秒。

        ReleaseOpacityAnimation = compositor.CreateScalarKeyFrameAnimation();
        ReleaseOpacityAnimation.InsertExpressionKeyFrame(0f, "This.CurrentValue", easeIn);
        ReleaseOpacityAnimation.InsertKeyFrame(1f, 0f, easeIn);
        ReleaseOpacityAnimation.Duration = TimeSpan.FromSeconds(0.2);
        ReleaseOpacityAnimation.DelayTime = TimeSpan.FromSeconds(0.2);
        ReleaseOpacityAnimation.StopBehavior = AnimationStopBehavior.LeaveCurrentValue;
        ReleaseOpacityAnimation.Target = "Opacity";
        //創建釋放後,特效Visual的透明度的關鍵幀動畫,持續0.2秒。

        ReleaseAnimationGroup = compositor.CreateAnimationGroup();
        ReleaseAnimationGroup.Add(ReleaseSizeAnimation);
        ReleaseAnimationGroup.Add(ReleaseOffsetAnimation);
        ReleaseAnimationGroup.Add(ReleaseOpacityAnimation);
        //創建動畫組
    }
}

萬事俱備,只欠東風,還記得Attach方法里給控制項掛上的PointerPressed和PointerReleased方法不?

這裡不能用+=和-=,因為Pointer的事件很特殊(怎麼個說法記不清了),必須要用到AddHandler的最後一個參數,HandlerEventToo為true,才能正確的處理。

private void Element_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    if (AssociatedObject is FrameworkElement element)
    {
        var point = e.GetCurrentPoint(element).Position.ToVector2();  //獲取點擊相對於控制項的坐標

        rectVisual.StopAnimationGroup(PressAnimationGroup);
        rectVisual.StopAnimationGroup(ReleaseAnimationGroup);
        //停止正在播放的動畫

        rectVisual.Offset = new Vector3(point.X, 0f, 0f);  //設置特效Visual的起始橫坐標為點擊的橫坐標,縱坐標為0
        rectVisual.StartAnimationGroup(PressAnimationGroup);  //開始按下的動畫
    }

}


private void Element_PointerReleased(object sender, PointerRoutedEventArgs e)
{
    rectVisual.StopAnimationGroup(PressAnimationGroup);
    rectVisual.StopAnimationGroup(ReleaseAnimationGroup);
    //停止正在播放的動畫
    rectVisual.StartAnimationGroup(ReleaseAnimationGroup);  //開始釋放的動畫
}

最後再寫一個Detach方法擦屁股就大功告成了:

public void Detach()
{
    if (AssociatedObject is UIElement element)
    {
        element.RemoveHandler(UIElement.PointerPressedEvent, new PointerEventHandler(Element_PointerPressed));
        element.RemoveHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(Element_PointerReleased));
    }
    //卸載事件

    rectVisual.StopAnimationGroup(PressAnimationGroup);
    rectVisual.StopAnimationGroup(ReleaseAnimationGroup);
    //停止動畫

    containerVisual.Children.Remove(rectVisual);
    //移除特效Visual
}

很輕鬆,不是嗎?

使用方法也很簡單:

<Page
    ...    
    xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:MyBehaviors="using:MyBehaviors"

    ...

    <Button>
        <Interactivity:Interaction.Behaviors>
            <MyBehaviors:ButtonBehavior />
        </Interactivity:Interaction.Behaviors>
    </Button>

 

把大象關冰箱,統共分幾步?

1、設置behavior,獲取到控制項對象;

2、在behavior中操作控制項對象;

3、移除behavior。

就這麼簡單。接下來又到了挖坑時間(話說上次滑動返回的坑還沒填...):

 


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

-Advertisement-
Play Games
更多相關文章
  • 進入vi的命令 vi filename :打開或新建文件,並將游標置於第一行首 vi +n filename :打開文件,並將游標置於第n行首 vi + filename :打開文件,並將游標置於最後一行首 vi +/pattern filename:打開文件,並將游標置於第一個與pattern匹配 ...
  • 前言 在安裝後hadoop之後,接下來需要安裝的就是Spark。 scala 2.11.7下載與安裝 具體步驟參見 "上一篇博文" Spark下載 為了方便,我直接是進入到了/usr/local文件夾下麵進行下載 Spark安裝之前的準備 文件的解壓與改名 為了我後面方便配置 ,在這裡我把文件夾的名 ...
  • 回到目錄 一般地,我們在VS里添加了一個解決方案之後,會更新模塊或者業務添加多個api,web項目,這類似於最近說的微服務,而我們的docker-compose與微軟體正好有了一種默契,當你為你的解決方案添加docker支持之後,你會發佈有個docker-compose出現了,它會把所有可以發佈的項 ...
  • 本文目錄: 1.4.1 環境變數 1.4.2 普通變數 1.4.3 修改變數的生命周期和作用域 1.4.4 獲取變數的長度 1.4.5 declare聲明變數 1.4.6 位置變數和特殊變數 1.4.7 shift輪替變數 1.4.8 shell其它基礎 1.4.9 變數的切分、提取和替換 變數存在 ...
  • 充分發揮Nginx的高效性和穩定性,對於Nginx優化非常重要。下麵主要是從編譯安裝、第三方插件、系統內核等三方面介紹。 編譯安裝過程優化 1、減小Nginx編譯後的文件大小 在編譯Nginx時,預設是以debug模式進行,而在debug模式下會插入很多跟蹤和ASSERT之類的信息,編譯完後,一個N ...
  • 站在用戶登錄的角度來說,shell分為兩種類型: 登錄式shell:如通過某終端登錄,使用su - username命令切換用戶。 非互動式shell:如使用su username命令切換用戶;圖形終端下打開命令終端;shell腳本。 更簡單但不精確的區分方法是:不需要輸入賬號密碼才能登陸的shel ...
  • 一、介紹 Tengine是由淘寶網發起的Web伺服器項目。它在Nginx的基礎上,針對大訪問量網站的需求,添加了很多高級功能和特性。Tengine的性能和穩定性已經在大型的網站如淘寶網,天貓商城等得到了很好的檢驗。它的最終目標是打造一個高效、穩定、安全、易用的Web平臺。 二、需求 由於目前項目組負 ...
  • 1:使用win+r打開 運行 控制台2:輸入 regedit 打開註冊表3:進入 1 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor\CompletionCharView Code將值改為十進位的 9 ;點擊確定4:以後運行CMD的時候... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...