UWP:使用Behavior实现Button点击动态效果

简介: 原文:UWP:使用Behavior实现Button点击动态效果废话不多说,先上效果 没有做成安卓那种圆形的原因是...人家真的不会嘛... 好了下面是正文: 首先在工程中引入Behavior的库,我们使用Nuget。
原文: UWP:使用Behavior实现Button点击动态效果

废话不多说,先上效果

没有做成安卓那种圆形的原因是...人家真的不会嘛...

好了下面是正文:

首先在工程中引入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。

就这么简单。接下来又到了挖坑时间(话说上次滑动返回的坑还没填...):

 

目录
相关文章
|
19天前
|
数据安全/隐私保护 开发者
六、ArkTS 常用组件-按钮(Button)/切换按钮(Toggle)/文本输出(TextInput)
`Button` 组件是 HarmonyOS 应用开发中的基本组件之一,主要用于响应用户的点击操作。它支持两种使用方式:不包含子组件和包含子组件。不包含子组件时,`Button` 通过 `label` 属性设置按钮上的文字,同时提供 `options` 参数来配置按钮类型和点击效果;包含子组件的方式则允许更灵活的内容展示,如图片或复杂布局,此时无需设置 `label`。此外,`Button` 组件还提供了设置背景颜色、边框圆角等样式的方法,以及绑定点击事件的功能,使开发者能够轻松实现丰富的交互体验。
22 0
六、ArkTS 常用组件-按钮(Button)/切换按钮(Toggle)/文本输出(TextInput)
SwiftUI—Button按钮控件的使用
SwiftUI—Button按钮控件的使用
525 0
SwiftUI—Button按钮控件的使用
|
前端开发 Windows
[UWP]使用Picker实现一个简单的ColorPicker弹窗
原文:[UWP]使用Picker实现一个简单的ColorPicker弹窗 在上一篇博文《[UWP]使用Popup构建UWP Picker》中我们简单讲述了一下使用Popup构建适用于MVVM框架下的弹窗层组件Picker的过程。
1147 0
|
前端开发 Windows 数据可视化
[UWP]使用Popup构建UWP Picker
原文:[UWP]使用Popup构建UWP Picker 在上一篇博文《[UWP]不那么好用的ContentDialog》中我们讲到了ContentDialog在复杂场景下使用的几个令人头疼的弊端。那么,就让我们在这篇博文里开始愉快的造轮子之旅吧! 首先要向大家说明:这篇博文主要还是写的构建Picker时的思考过程,如果不感兴趣的,可以直接略过这篇,阅读下一篇《[UWP]如何使用Picker实现一个简单的ColorPicker弹窗》。
1037 0
|
C# Windows
[UWP]实现Picker控件
原文:[UWP]实现Picker控件 1. 前言 在WPF中,很多打开下拉框(Popup或Flyout)选择一个结果值的控件,除了ComboBox等少数例外,这种控件都以-Picker做名称后缀。因为要打开关闭下拉框和计算下拉框的弹出位置, 这类控件实现起来还挺麻烦的。
862 0
|
测试技术 图形学
Unity3D UGUI下拉菜单/Dropdown组件用法、总结
Unity3D中UGUI实现下拉菜单 本文提供全流程,中文翻译。 Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例) Chinar...
4079 0