廢話不多說,先上效果 沒有做成安卓那種圓形的原因是...人家真的不會嘛... 好了下麵是正文: 首先在工程中引入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。
就這麼簡單。接下來又到了挖坑時間(話說上次滑動返回的坑還沒填...):