废话不多说,先上效果
没有做成安卓那种圆形的原因是... 人家真的不会嘛...
好了下面是正文:
首先在工程中引入 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。
就这么简单。接下来又到了挖坑时间(话说上次滑动返回的坑还没填...):
来源: http://www.cnblogs.com/blue-fire/p/7237158.html