zoukankan      html  css  js  c++  java
  • 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。

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

  • 相关阅读:
    贪心法
    div 样式
    echarts标题(title)配置
    利用svg画路径图 vue
    vue 杂项
    Charset 0x0408D00000/MS936 is not supported by the JVM
    Android开发中Eclipse常用快捷键
    Java 中强制删除文件的方法
    利用html5的localStorage结合jquery实现日常费用查询器
    Ant编译utf8非法字符:/65279 解决方法
  • 原文地址:https://www.cnblogs.com/blue-fire/p/7237158.html
Copyright © 2011-2022 走看看