0、说明1

表达式下面两种写法效果是一样的:

{
  element: el,
  property: 'background-color',
  expression: {
    origin:'evaluateColor("#FF5000", "#000000", min(t, 300)/300)'
  }
},
{
  element: el,
  property: 'background-color',
  expression: 'evaluateColor("#FF5000", "#000000", min(t, 300)/300)'
},

0:说明2

因为在 web 环境和 weex 环境需要传递的DOM节点不一样,因此需要区分两种环境,引入 universal-env,并且在获取节点的时候,使用下面的 getEl 方法。

import {isWeex} from 'universal-env';
function getEl(el) {
  return isWeex ? findDOMNode(el).ref : findDOMNode(el);
}

比如在 Rax 代码中:

const el = getEl(this.refs.box);
const box1 = getEl(this.refs.box1);
const box2 = getEl(this.refs.box2);

一、说明

BindingX 官网:https://alibaba.github.io/bindingx/guide/cn_api_interpolator

代码使用 Rax 开发:https://alibaba.github.io/rax/

之前写过一篇入坑 bindingx 的文章,文章地址:

bindingx 确实不错,不过自己在使用的时候也遇到过很多问题,对于整个文档的理解其实也不到位,因此后面研究了一下文档,并且以现有的实例进行分析,实现一部分实例。

这篇文章主要研究的是缓动函数颜色估值函数 两部分,对于基础的 BindingX 的使用不再重复。

研究这两部分其实主要是看了下面的这个demo:

GIF.gif

二、分析

要想实现上面的 gif 的示例,最关键的两部分其实就是 缓动函数 和 颜色估值函数。

为了更简单的说明,做了个简化:

  • 展开菜单有 A-B-C 缩减成 1-2 两个
  • 去掉了展开后的回弹效果
  • 无论是展开还是回收,均使用 300ms
  • 无论展开还是回收,统一开始和停止时间,不做时差效果

需要做到的效果如下:

G2IF.gif

首先分析需要 bindingx 进行绑定的元素:

  • 绑定 1-2 两个展开菜单
  • 绑定开始便显示的 + 圆形菜单的旋转
  • 绑定开始便显示的 + 圆形菜单的颜色变化

分析需要的 eventType

上面示例中其实是不需要 pan、scroll 这种事件类型,点击通过 rax 的 rax-touchableonPress 触发。

需要 bainding 的是 timing 事件类型,当我点击之后,在某个时间段内,完成三个菜单展开和收起的效果。

三、缓动函数

缓动函数在 BindingX 的文档位置是 差值器 :

缓动函数是使用在表达式中的,比如下面的代码:

{
  element: box1,
  property: 'transform.translateY',
  expression: {
    origin:'linear(t,0,-110,300)'
  }
},

关键点在 linear(t,0,-110,300),文档中的体现是 easingFunction(t, begin, changeBy, duration),

各个属性说明如下:

字段说明
easingFunction方法名,以具体函数代替,比如 easeInSine
t时间值,动画流逝的时间
begin属性起始值
changeBy属性变化值。即(结束值-起始值) 这个很重要
duration动画时长

知道了属性说明,其实就好做很多了,比如我需要一个元素从 0 沿 Y 轴移动 100px,时间为 300ms,使用 linear 函数写法如下:

linear(t, 0, 100, 300);

其他缓动方法也是如此。

四、颜色估值

在修改如背景色文字颜色这种需求时,通常会用到颜色变化函数。

颜色变化函数有 rgbrgba 以及 颜色估值 三种方式,为了简单起见,我这里使用了颜色估值,不过这个颜色估值貌似有点问题,下面展开。

考虑到颜色值通常是以#rrggbb格式书写的,如果要做渐变,先要计算各个颜色分量的int值,较繁琐。因此,我们还提供了一个颜色计算的估值器函数 evaluateColor

颜色估值方法的表达式如下: evaluateColor('beginColor','endColor',fraction)

相关说明:

属性说明
evaluateColor固定方法名
beginColor起始颜色,如 '#000000'
endColor结束颜色,如 '#FFFFFF'
fraction进度(Double类型,范围是 0.0~1.0)。如在动画场景下,即 (min(t,duration)/duration)

如果你需要颜色从 #000000#FF5000,时间为 300ms,则估值方法如下:

evaluateColor('#000000','#FF5000',min(t,300)/300)

上面提到颜色估值可能有问题,这是因为我用上面的表达式,在 web 环境是 ok 的,最终颜色变成了 #FF5000,但是在 weex 环境却变成了另外的颜色。

web 环境:

1540315696(1).jpg

weex 环境:

349851970751096190.jpg

目前不清楚原因,需要在排查下。

后面发现原因,是我写错了,颜色从 #FF500 写成了 #FF5000

五、实现菜单

1、点击事件及状态控制

实现菜单目前就比较简单了,之前说了,需要在触发 onPress 的时候进行 timing 的 bindingX,而我有需要展开和收起两种形式,因此就需要一个 state 去控制和声明当前应该执行展开操作还是收起操作。

state = {
  isExpanded: false
}

因此点击事件处理如下:

  • this.expand() 用于处理展开
  • this.collspan() 用于处理收起
  • 最终操作结束后,需要重置 this.state.isExpanded = !this.state.isExpanded
  onPressHandle = () => {
    const {isExpanded} = this.state;
    if (!isExpanded) {
      this.expand();
    } else {
      this.collspan();
    }
    this.setState({
      isExpanded: !isExpanded
    });
  }

2、expand() 展开

上面也说了,实际上需要对两个子菜单和默认显示的菜单进行绑定,因此需要绑定四个 props,根据需要效果,使用 linear 两个子菜单的展开,其他的不多赘述,重点说明 linearevaluateColor

  expand = () => {
    const el = getEl(this.refs.box);
    const box1 = getEl(this.refs.box1);
    const box2 = getEl(this.refs.box2);
    BindingX.bind({
      eventType: 'timing',
      exitExpression:{
        origin:'t>300'
      },
      props: [
        {
          element: el,
          property: 'transform.rotate',
          expression: {
            origin:'linear(t,0,45,300)'
          }
        },
        {
          element: el,
          property: 'background-color',
          expression: {
            origin:'evaluateColor("#000000", "#FF5000", min(t, 300)/300)'
          }
        },
        {
          element: box1,
          property: 'transform.translateY',
          expression: {
            origin:'linear(t,0,-110,300)'
          }
        },
        {
          element: box2,
          property: 'transform.translateY',
          expression: {
            origin:'linear(t,0,-220,300)'
          }
        },
      ]
    });
  }

上面 expand() 方法中,exitExpression 表示边界,当 t>300 的时候,就停止动画。

主菜单的旋转绑定中,属性是 transform.rotate,方法是 linear(t,0,45,300),实际上就是从 0deg -> 45deg,时间 300ms

主菜单的颜色变化绑定中,属性是 background-color,方法是 evaluateColor("#000000", "#FF500", min(t, 300)/300),实际上就是 300ms 的背景色的变化

子菜单1的绑定中,属性是 transform.translateY,方法是 linear(t,0,-110,300),实际上就是 300ms 内,从沿Y轴移动 0 -> 110

子菜单2的绑定中,属性是 transform.translateY,方法是 linear(t,0,-220,300),实际上就是 300ms 内,从沿Y轴移动 0 -> 220

3、collspan() 收起

collspan = () => {
    const el = getEl(this.refs.box);
    const box1 = getEl(this.refs.box1);
    const box2 = getEl(this.refs.box2);
    BindingX.bind({
      eventType: 'timing',
      exitExpression:{
        origin:'t>300'
      },
      props: [
        {
          element: el,
          property: 'transform.rotate',
          expression: {
            origin: 'linear(t,45,-45,300)'
          }
        },
        {
          element: el,
          property: 'background-color',
          expression: {
            origin: 'evaluateColor("#FF500", "#000000", min(t, 300)/300)'
          }
        },
        {
          element: box1,
          property: 'transform.translateY',
          expression: {
            origin: 'linear(t, 0-110, 110, 300)'
          }
        },
        {
          element: box2,
          property: 'transform.translateY',
          expression: {
            origin: 'linear(t, 0-220, 220, 300)'
          }
        },
      ]
    });
  }

主菜单的背景色和旋转就不过多阐述了,主要说一下子菜单,以第二个子菜单举例

子菜单在展开的时候,transform.translateY 从 0 变成了 -220,因此在收起的时候,需要从 -220 变成 0,因此缓动函数是 linear(t, 0-220, 220, 300)请注意,缓动函数的第三个属性是指变化,而不是结束时的值,因此这里实际上是变化了 220,也就是 0-(-200)

六、代码

最终代码和效果见 playground