当前位置: 首页 > 文档资料 > BindingX 中文文档 >

示例 - 动画

优质
小牛编辑
128浏览
2023-12-01

动画、时间轴事件监听

本文将向您介绍如何使用 BindingX 来实现动画。如果您还不了解 BindingX 的工作原理,强烈建议先阅读文档 《简介》以及 《核心概念》。

特性介绍

要在 BindingX 中使用动画,需要将eventType的值设置为 timing 。在timing模式下, BindingX 提供了一个预置变量叫 t ,可以参与表达式运算,它代表的含义是动画流逝的时间,从0开始,与手势不同的是, timing 中是不需要指定锚点(anchor)的。具体可以参考文档《支持的事件类型》。

如何使用

下面通过一个简单的例子来介绍如何使用。在教程 《手势》中,我们实现了通过手势横滑卡片的效果。但是当手指离开屏幕时,卡片并不能移除或者复位。这篇教程将解决这个问题。下面的动图是最终的效果。

binding_card_timing.gif | center | 320x562

注意: 我们的例子使用weex实现,上层DSL选用Rax。

第一步: 项目准备

  1. 使用 Rax 编写卡片布局;

  2. 安装 BindingX 组件并引入;

  3. 给卡片注册onTouchStart事件;

    1. 在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多种插值器供您使用! 您甚至还可以使用贝塞尔曲线来定制您的插值效果。具体使用可以参考文档 《插值器/缓动函数》。

现在我们希望在手势结束时根据位置判断卡片是复位还是删除。那么根据卡片的偏移量就可以进行判断了。有下面三种情况:

  1. 复位

  2. 向左移除

  3. 向右移除

同时,我们假设动画的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;
    }
  ...
}

最后

到这里,我们已经实现了"卡片侧滑删除"的效果。