示例 - 动画
动画、时间轴事件监听
本文将向您介绍如何使用 BindingX
来实现动画。如果您还不了解 BindingX
的工作原理,强烈建议先阅读文档 《简介》以及 《核心概念》。
特性介绍
要在 BindingX
中使用动画,需要将eventType的值设置为 timing
。在timing模式下, BindingX
提供了一个预置变量叫 t
,可以参与表达式运算,它代表的含义是动画流逝的时间,从0开始,与手势不同的是, timing
中是不需要指定锚点(anchor)的。具体可以参考文档《支持的事件类型》。
如何使用
下面通过一个简单的例子来介绍如何使用。在教程 《手势》中,我们实现了通过手势横滑卡片的效果。但是当手指离开屏幕时,卡片并不能移除或者复位。这篇教程将解决这个问题。下面的动图是最终的效果。
注意: 我们的例子使用weex实现,上层DSL选用Rax。
第一步: 项目准备
使用
Rax
编写卡片布局;安装
BindingX
组件并引入;给卡片注册onTouchStart事件;
在onTouchStart中通过
BindingX
绑定表达式,实现拖拽卡片和更新透明度的效果。
以上步骤在教程 《手势》中已经介绍过了,这里不再展开。
第二步: 记录状态
现在我们希望在手势结束时根据位置判断卡片是复位还是删除。那么,首先我们需要监听手势结束的事件,然后记录卡片的透明度以及位置。代码如下:
gesToken=0;
x = 0; // 卡片偏移量
isInAnimation = false; // 卡片是否在动画当中
opacity = 1; // 卡片透明度
onTouchStart = (event)=>{
var my = this.refs.my;
var self = this;
var gesTokenObj = bindingx.bind({
anchor:my,
eventType:'pan',
props: [
{
element:my,
property:'transform.translateX',
expression:'x+0'
}
]
}, function(e) {
if(e.state === 'end') {
self.x = e.deltaX;
self.opacity = 1-Math.abs(e.deltaX)/600;
}
});
this.gesToken = gesTokenObj.token;
}
同时,在手势结束的时候,我们需要绑定一个动画。因此,我们增加一个新的方法 bindTimeing
。代码如下:
if(e.state === 'end') {
self.x = e.deltaX;
self.opacity = 1-Math.abs(e.deltaX)/600;
self.bindTiming();
}
...
bindTiming = ()=>{
//TODO
}
第三步:使用表达式描述动画
回顾您之前编写动画的经历,我们在声明动画的时候,通常需要设置动画的时长(duration)、需要改变的属性(property)以及动画的插值器如fadeIn等。但是,在使用 BindingX
的时候,您必须要要通过"表达式"的形式来描述。事实上,一个匀速动画可以用以下表达式来表示:
start + (end-start)*(min(t,duration)/duration)
start和end分别是起始值和结束值,duration就是动画时长。举个例子,假设,你希望改变一个view的translateX
属性,让其在2s内从0变化到650。那么你可以这样写:
props: [
{
element:some_ref,
property:'transform.translateX',
expression: '0+(650-0)*min(t,2000)/2000'
}
]
以上是匀速动画,那么,如果我们希望实现一个非匀速动画应该怎么办呢? 答案依然是编写表达式。 前面提到,匀速动画的表达式是start+(end-start)\*(min(t, duration) / duration)
,那么在这个基础上增加插值器,就可以实现非匀速动画,而插值器实际上是一个函数,形如:
input = min(t, duration)/duration
f(interpolator) = f(input)
考虑到插值器算法比较通用,因此, BindingX
内置了30多种插值器供您使用! 您甚至还可以使用贝塞尔曲线来定制您的插值效果。具体使用可以参考文档 《插值器/缓动函数》。
现在我们希望在手势结束时根据位置判断卡片是复位还是删除。那么根据卡片的偏移量就可以进行判断了。有下面三种情况:
复位
向左移除
向右移除
同时,我们假设动画的duration是1000ms。向左和向右移除的动画插值器选择 easeOutExpo
,复位的动画插值器选择 easeOutElastic
。(如果不了解插值器的用法,请参考文档《插值器/缓动函数》)。
代码如下:
bindTiming = ()=> {
this.isInAnimation = true;
var my = this.refs.my;
var self = this;
//should equal with timing duration
var exit = "t>1000";
var changed_x;
var final_x;
var final_opacity;
var translate_x_expression;
var shouldDismiss = false;
if(self.x>=-750/2 && self.x<=750/2) { // 复位
shouldDismiss = false;
final_x = 0;
changed_x = 0-self.x;
final_opacity = 1;
translate_x_origin = "easeOutElastic(t,"+self.x+","+changed_x+",1000)";
} else if(self.x < -750/2) { // 向左移除
shouldDismiss = true;
final_x = -750;
changed_x = -750-self.x;
final_opacity = 0;
translate_x_origin = "easeOutExpo(t,"+self.x+","+changed_x+",1000)";
} else {//x > 750 向右移除
final_x = 750;
shouldDismiss = true;
changed_x = 750-self.x;
final_opacity = 0;
translate_x_origin = "easeOutExpo(t,"+self.x+","+changed_x+",1000)";
}
var changed_opacity = final_opacity - self.opacity;
var opacity_expression = "linear(t,"+self.opacity+","+changed_opacity+",1000)";
var result = bindingx.bind(
{
eventType:'timing',
exitExpression: exit,
props: [
{
element:my,
property:'transform.translateX',
expression: translate_x_expression
},
{
element:my,
property:'opacity',
expression:opacity_expression
}
]
},function(e){
if(e.state === 'end' || e.state === 'exit') {
// reset x
self.x = final_x;
self.isInAnimation = false;
if(shouldDismiss) {
//remove card from hierarchy
}
}
});
}
第四步: 效果完善
如果在动画执行过程中,应该禁止手势,防止状态发生错乱。因此,在onTouchStart方法中需要增加如下代码:
onTouchStart = (event)=>{
var self = this;
if(this.isInAnimation === true) {
if(this.gesToken != 0) {
bindingx.unbind({
eventType:'pan',
token:self.gesToken
})
this.gesToken = 0;
}
return;
}
...
}
最后
到这里,我们已经实现了"卡片侧滑删除"的效果。