废话很少说,先上效果工具
没有作成安卓那种圆形的缘由是...人家真的不会嘛...动画
好了下面是正文: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。
就这么简单。接下来又到了挖坑时间(话说上次滑动返回的坑还没填...):