bindingx 表达式动画

表达式(Expression)

表达式,是由数字、运算符、变量等以能求得数值的有意义排列方法所得的组合。譬如, x\*3+10 就是一个表达式,当x被赋值时,整个表达式就会有一个明确的结果。通过表达式,我们就可以描述一个具体的交互行为,比如我们希望x从0变化到100时,透明度能从1变化到0.5,那么表达式可以描述为: f(alpha) = 1-(x/100)*0.5。

在 BindingX 中,我们实现了一个轻量的表达式解析引擎用于执行表达式,除了基本的四则运算外,还支持三元运算符、数学函数等高级语法,能够满足绝大部分的场景。要查看 BindingX 中支持的表达式语法,请参考文档: 《支持的表达式语法》。

事件类型(Event Type)

现在你已经知道了 BindingX 是通过表达式来描述交互行为的,那么表达式中的变量究竟是什么呢?
答案是”不同的事件类型拥有不同的表达式变量”!那什么是事件类型呢?在 BindingX中,事件是指能够驱动表达式数值变化的数据产生者,比如”用户的手势”、”列表的滚动” 甚至是”陀螺仪感知到的方向变化”,每一种这样的事件都对应着唯一的事件类型,比如”手势”对应的事件类型就是 pan ,要查看 BindingX 支持的所有事件类型,请参考文档: 《支持的事件类型》。

另外还需要说明的是,每一种事件类型都对应着不同的表达式变量。比如,当事件类型为 pan 时,表达式变量就是x和y,分别代表手势过程中横向和纵向的偏移量。同样地,要查看每一种事件类型对应的表达式变量,请参考文档: 《支持的事件类型》。

属性变换(Transformation Properties)

表达式的执行结果最终会驱动UI变化,比如透明度、位移、背景色等, 属性变换 就是用来描述这些属性的。在 BindingX 中,支持常用的transform属性变换,如translationscale ,另外还包括透明度、宽高等属性。要查看所有支持的属性变换,请参考文档:《支持的属性》

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

前置条件

  1. 学习如何使用 weex 或者 ReactNative 搭建页面;

  2. 在您的Android或者iOS工程中,添加 BindingX 组件依赖,并注册组件。

第一步: 安装依赖

  1. 安装npm依赖

$ npm install weex-bindingx --save
  1. 在JS代码中引入BindingX模块

import BindingX from weex-bindingx;

第二步: 编写表达式

  1. 根据业务场景,选择您需要的EventType。 比如,要监听手势,evenType值为pan,监听滚动容器scrollOffset变化,eventType值为scroll。

  2. 根据交互行为,选择要改变的属性,并编写相应的表达式。比如,交互行为是”用户横滑100单位,透明度从1变化到0″。则属性为”opacity”,表达式为”1-x/100″。

第三步: 绑定表达式

根据第二步得到的eventType、Expression以及Property,调用 BindingX 模块的 bind方法,完成绑定。

 var result = BindingX.bind({
     eventType: 'pan',       ==> 事件类型
     anchor: 'foo',          ==> anchor指的是事件的触发者,如果是eventType是"orientation"或"timing",则不用填
     props: [
         {
             element: view.ref,     ==> 要改变的视图的引用或者id
             expression: "1-x/100", ==> 表达式
             property: "opacity"    ==> 要改变的属性
         }
     ] 
 })

当调用bind方法之后,Native会启动监听,当目标事件(比如手指滑动、设备方向变化等)发生的时候,便会执行您先前绑定的一组或者多组表达式。 bind 方法会返回一个JS对象,其中包含了一个 token 属性,可以使用这个token取消绑定。

第四步: 取消绑定

在合适的时机调用 BindingX 的unbind方法取消绑定。比如,页面不可见或者即将销毁的时候。

BindingX.unbind({
    token: result.token,
    eventType: 'pan'
})

到这里,您已经学会基本的API使用了,更多内容可以参考其他教程。

手指移动事件监听

本文将向您介绍如何使用 BindingX 提供的手势能力来完成流畅的交互效果。如果您还不了解 BindingX 的工作原理,强烈建议先阅读文档 《简介》以及 《核心概念》。

特性介绍

BindingX 目前仅支持 pan 手势,您只需要在 bind 方法中将 eventType 的值设置为 pan 即可使用。 bindingX 提供了x和y两个预置变量,可以参与表达式运算,这两个变量的含义分别是手指移动过程中横向和纵向的偏移量,具体可以参考文档 《支持的事件类型》。

如何使用

下面通过一个简单的例子来介绍如何使用。我们的任务是实现一个”卡片侧滑删除”的效果。


bindingx_card_1.gif | center | 320x562

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

第一步: 使用Rax编写卡片布局

Rax 提供了类似 ReactJS 的语法,如果您还不了解,可以移步他们的 官方网站 进行学习。
首先,您需要使用Rax-CLI工具创建一个新的Rax工程。然后就可以开始编写页面模板了。创建一个文件,命名为index.js,增加如下代码:

import {createElement, Component, render} from 'rax';
import Text from 'rax-text';
import View from 'rax-view';

class App extends Component {
  render() {
  }
}
render(<App />);

然后在当前文件夹下创建一个新文件,命名为index.css,用于编写样式。然后在index.js中引入该样式文件。

import {createElement, Component, render} from 'rax';
import Text from 'rax-text';
import View from 'rax-view';

import './index.css';

随后,我们在index.js中基于JSX语法编写卡片的布局,并在index.css中编写相关样式。

index.js

class App extends Component {
  render() {
    return (
      <div className="container" >
        <div className="border">
          <div class="box">
            <div className="head">
              <div className="avatar"></div>
              <text className="username">Foo</text>
            </div>
            <div className="content">
              <text className="desc">Weex is a framework for building Mobile cross-platform UI. Rax is a universal JavaScript library with a largely React-compatible API.</text>
            </div>
            <div className="footer">
              <text className="action">SHARE</text>
              <text className="action" style={{color:'#7C4DFF'}}>EXPLORE</text>
            </div>
         </div>
        </div>
      </div>
    );
  }
}

index.css

 .container {
    flex: 1;
    background-color:#eeeeee;
  }
  .border{
    height:1000;
    padding-left:35;
    padding-right:35;
    padding-top:100;
  }
  .box {
    width: 680;
    height: 450;
    background-color:#651FFF;
  }
  .head {
    background-color:#651FFF;
    width:680;
    height:120;
    flex-direction:row;
    align-items:center;
  }
  .content{
    width:680;
    height:240;
    background-color:#651FFF;
    padding-left:24;
    padding-top:24;
    padding-right:24;
    box-sizing:border-box;
  }
  .footer {
    width:680;
    height:90;
    background-color: #fff;
    align-items:center;
    justify-content:flex-end;
    padding-right:25;
    flex-direction:row;
    box-sizing:border-box;
  }
  .action {
    font-size:35;
    padding-right:20;
  }
  .desc {
    font-size:32;
    color:#fff;
    padding-left:24;
  }
  .avatar {
    width:96;
    height:96;
    border-radius:48;
    background-color:#CDDC39;
    margin-left:36;
    margin-right:48;
  }
  .username {
    color:#fff;
    font-size:32;
  }

现在,我们的卡片布局已经编写完毕,您可以编译、预览效果。如果没有问题,就可以进行下一步了!

第二步: 引入BindingX组件

  1. 首先通过npm安装依赖。

$ npm install weex-bindingx --save
  1. 在index.js的头部引入 BindingX 组件。

import {createElement, Component, render} from 'rax';
import Text from 'rax-text';
import View from 'rax-view';
import BindingX from weex-bindingx;

import './index.css';

第三步: 注册手势事件

  1. 为卡片增加ref属性,用于唯一标识该组件,随后 BindingX 就可以基于ref找到该组件。

    render() {
        return (
          <div className="container" >
            <div className="border">
                <div ref='my' class="box" >
                  <div className="head">
                   <div className="avatar"></div>
                   <text className="username">Foo</text>
                  </div>
                  ...
                </div>
            </div>
          </div>
        );
    }
  2. 为卡片注册 onTouchStart 事件,用于绑定表达式。

    onTouchStart = (event)=>{
    
    }
    
    render() {
        return (
          <div className="container" >
            <div className="border">
                <div class="box" onTouchStart={this.onTouchStart}>
                  <div className="head">
                   <div className="avatar"></div>
                   <text className="username">Foo</text>
                  </div>
                  ...
                </div>
            </div>
          </div>
        );
    }

第四步: 实现卡片拖拽效果

很显然,横滑的表达式应该是 x+0 ,要改变的属性是 transform.translateX 。手势绑定的代码如下:

gesToken=0;

onTouchStart = (event)=>{
    var my = this.refs.my;
    var gesTokenObj = BindingX.bind({
          anchor:my,
          eventType:'pan',
          props: [
              {
                element:my, 
                property:'transform.translateX',
                expression:'x+0'
              }
            ]
        }, function(e) {
          // nope
        });
     this.gesToken = gesTokenObj.token;
}

重新编译、执行代码,您应该就能横向拖拽卡片了!到这里,我们已经完成了大部分的效果,使用 BindingX 就是这么简单。

第五步: 增加透明度变化


在,我们在原来的基础上,再增加一个需求。手指在横滑的过程中,我们希望卡片的透明度也跟着变化。卡片的透明度对应的属性是opacity,值的范围是0
到1,因此,我们表达式的计算结果应该是0到1之间。在这个例子里,我们假设希望手指横向滑动600个单位,透明度变为0,那么表达式应该是 1-abs(x)/600 。

我们在props中再增加一段配置:

gesToken=0;

onTouchStart = (event)=>{
    var my = this.refs.my;
    var gesTokenObj = BindingX.bind({
          anchor:my,
          eventType:'pan',
          props: [
              {
                element:my, 
                property:'transform.translateX',
                expression:'x+0'
              },
              {
                element:my, 
                property:'opacity',
                expression:'1-abs(x)/600'
              }
            ]
        }, function(e) {
          // nope
        });
     this.gesToken = gesTokenObj.token;
}

重新编译、执行,您应该就能看到期望的效果啦!

最后


顾这个例子,我们给卡片绑定了两个表达式,分别用于控制卡片的位移以及透明度,当手指在卡片上横向移动时,就会触发对应的表达式,进而改变卡片的位置以及
透明度。然而,这个例子仍然不完善,因为当我们的手指释放时,卡片也停住不动了,而我们希望卡片能够根据结束时的位置自动移除到屏幕外或者是回到原点。查
看下一篇教程 《动画》,我们来解决这个问题。

动画、时间轴事件监听

本文将向您介绍如何使用 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;
    }
  ...
}

最后

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
中缀表达式转后缀表达式是一种常用的算法,也称为逆波兰表达式。下面我将以动画的形式展示中缀表达式转后缀表达式的过程。 首先,让我们来看一个例子:中缀表达式 "3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3"。 步骤1:创建一个空的栈和一个空的输出队列。我们将使用栈来保存操作符,并且输出队列将保存后缀表达式。 步骤2:从左到右遍历中缀表达式的每个元素: - 如果遇到操作数(数字),直接将其添加到输出队列中。 - 如果遇到左括号 '(',将其压入栈中。 - 如果遇到右括号 ')',则将栈中的操作符弹出并添加到输出队列中,直到遇到左括号为止。 - 如果遇到操作符,先判断栈顶操作符的优先级: - 如果栈顶操作符的优先级大于当前操作符的优先级,将栈顶操作符弹出并添加到输出队列中,直到栈顶操作符的优先级小于或等于当前操作符的优先级。 - 最后,将当前操作符压入栈中。 步骤3:重复步骤2,直到处理完所有的输入元素。 步骤4:将栈中剩余的操作符依次弹出并添加到输出队列中。 最后,输出队列中的元素就是转换后的后缀表达式。 现在,让我们来观察一个实际的动画演示来理解这个过程。请点击以下链接查看动画演示: [中缀表达式转后缀表达式动画演示](https://www.cs.usfca.edu/~galles/visualization/StackArray.html) 希望这个动画演示能帮助你更好地理解中缀表达式转后缀表达式的过程!如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值