废话不多说,先上效果

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

好了下面是正文:

首先在工程中引入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 > && element.ActualHeight > )
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();
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();
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();
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。

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

UWP:使用Behavior实现Button点击动态效果的更多相关文章

  1. Android笔记——Button点击事件几种写法

    Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...

  2. Android开发-之监听button点击事件

    一.实现button点击事件的方法 实现button点击事件的监听方法有很多种,这里总结了常用的四种方法: 1.匿名内部类 2.外部类(独立类) 3.实现OnClickListener接口 4.添加X ...

  3. .net学习之母版页执行顺序、jsonp跨域请求原理、IsPostBack原理、服务器端控件按钮Button点击时的过程、缓存、IHttpModule 过滤器

    1.WebForm使用母版页后执行的顺序是先执行子页面中的Page_Load,再执行母版页中的Page_Load,请求是先生成母版页的控件树,然后将子页面生成的控件树填充到母版页中,最后输出 2.We ...

  4. button点击传多个参数

    // --------------------button点击传多个参数------------------------ UIButton *btn = [UIButton buttonWithTyp ...

  5. Android学习-----Button点击事件几种写法

    Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...

  6. 自学Android的第一个小程序(小布局、button点击事件、toast弹出)

    因为上班,学习时间有限,昨晚才根据教程写了一个小程序,今天忙里偷闲写一下如何实现的,来加深一下印象. 首先创建一个Android项目, 通过activity_xxx.xml布局文件来添加组件来达到自己 ...

  7. 两个堆叠fragment,上层fragment响应于降低fragment的button点击事件补救措施

    加入onViewCreated的Touch事件监听, 以解决叠在一起的fragment上层响应下层的button点击事件解决方法 @Override public void onViewCreated ...

  8. (转)Ext.Button点击事件的三种写法

    转自:http://maidini.blog.163.com/blog/static/377627042008111061844345/ ExtJs的写法太灵活了,现在收集了关于Button点击事件的 ...

  9. RxJava RxBinding 按钮(Button) 点击(click)

    /********************************************************************* * RxJava RxBinding 按钮(Button) ...

随机推荐

  1. Coursera 机器学习笔记(一)

    主要是第一二周内容 机器学习概要 机器学习是什么? 生活在信息时代的我们,其实时时刻刻都离不开机器学习算法.比如日常使用的搜索引擎就涉及到很多学习算法. Arthur Samuel 给出第一个定义.他 ...

  2. 高防TTCDN

    TCDN是深圳市云中漫网络科技公司高防CDN产品的品牌名称,既可以防御,也可以达到加速的效果,价格实惠.TTCDN适用于WEB应用,可以隐藏源站服务器IP,有效的减轻源站服务器压力,加快全国各地区线路 ...

  3. ArcGIS Earth(原谷歌地球)如何获取高精度矢量地图数据?(shp文件/要素类/kml)

    大家好,这次来分享干货.做地理分析的同学,或者需要使用地图却不知道哪里有矢量数据的时候,怎么办呢? 这次,我就告诉大家哪里能自己手工制作矢量点线面数据!注意哦,是自己绘制的. 使用到的软件: ArcG ...

  4. js删除 object中的空值

    var data = { a: 'a', b: '' } 删除 b和''的配对, /** * Delete all null (or undefined) properties from an obj ...

  5. Testlink研究小结

    1.Redmine与Testlink的关联 (1)redmine中的项目对应testlink的测试项目 (2)testllink执行用例时发现的问题可以记录到redmine中 2.Testlink优点 ...

  6. An overnight dance in discotheque

    An overnight dance in discotheque time limit per test 2 seconds memory limit per test 256 megabytes ...

  7. 我的学习之路_第二十一章_JDBC连接池

    JDBC连接池和DButils [DBCP连接池工具类] 使用读取配置文件的方式 DBCP中有一个工厂类 BasicDataSourceFactory 工厂类中有一个静态方法 返回值为: DataSo ...

  8. 百度搜索效果(jsonp法)

    百度搜索效果(jsonp法): 不需要放在服务器中,本地就可以执行 <!DOCTYPE html> <html> <head> <meta charset=& ...

  9. JS数组及内置对象

    [JS中的数组]1.数组的概念:数组是在内存中连续存储多个有序元素的结构元素的顺序,称为下标,通过下标查找对应元素.2.数组的声明: ① 字面量声明: var arr1 = [];JS中同一数组,可以 ...

  10. WCF学习——构建第二个WCF应用程序(六)

    一.创建客户端应用程序 若要创建客户端应用程序,你将另外添加一个项目,添加对该项目的服务引用,配置数据源,并创建一个用户界面以显示服务中的数据.若要创建客户端应用程序,你将另外添加一个项目,添加对该项 ...