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

廢話很少說,先上效果工具

沒有作成安卓那種圓形的緣由是...人家真的不會嘛...動畫

好了下面是正文:spa

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

在項目->引用上點擊右鍵,點擊管理Nuget程序包,而後瀏覽裏搜索Microsoft.Xaml.Behaviors.Uwp.Managedxml

或者在程序包管理控制檯裏(若是輸出右邊沒有這個標籤,使用工具->Nuget包管理器->程序包管理控制檯打開),輸入命令對象

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

回車,坐等,引入成功。blog

而後咱們新建一個類,名字叫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>

 

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

一、設置behavior,獲取到控件對象;

二、在behavior中操做控件對象;

三、移除behavior。

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

相關文章
相關標籤/搜索