简介
BindingX
是解决weex
和React Native
上富交互问题的一种解决方案。
它提供了一种称之为 "Expression Binding"
的机制可以在 weex
、React Native
上让手势等复杂交互操作以60fps的帧率流畅执行,而不会导致卡顿,因而带来了更优秀的用户体验。
背景
(可翻译为: Why is this challenging)
由于 weex
底层使用的 JS-Native Bridge
具有天然的异步特性,这使得 JS
和 Native
之间的通信会有固定的性能损耗,因此在一些复杂的交互场景中,JS
代码很难以高帧率运行。举个例子,如果我们要实现 "视图随手势移动" 的效果,那么按照传统的方式,需要在这个视图上绑定 touch
或者 pan
事件,当手势发生时, Native
会将手势事件通过 Bridge
传递给 JS
, 这产生了一次 Native
到 JS
的通信。而 JS
在接收到事件后,需要根据手指移动的偏移量驱动界面变化,这又会产生一次 JS
到 Native
的通信。与此同时,手势回调事件触发的频率是非常高的,频繁的的通信带来的时间成本很可能导致界面无法在16ms中完成绘制,进而产生卡顿。
事实上,不仅仅是在 weex
上存在这种问题, React Native
等框架同样存在类似的问题。拿 React Native Animated 组件为例,为了实现流畅的动画效果,这个组件采用了声明式的API,在 JS
端仅仅定义了输入与输出以及具体的 transform
行为,而真正的动画是通过 Native Driver
在 Native
层执行,这样就避免了频繁的通信。然而,这个方案只能解决一部分问题,如果是有复杂交互操作的场景就不够用了。另外,声明式的方式能够定义的行为非常有限,无法满足更复杂的交互场景。
原理介绍
我们通过探索,提出了一种全新的方式用来解决这个问题,方案称之为 Expression Binding
。
还是拿 "视图随手势移动" 这个场景举例。我们的方案是这样的:
在手势开始前, JS
将具体的手势控制行为以 "表达式" 的方式传递给 Native
,我们定义了两个变量 x
和 y
,分别代表手势过程中横向和纵向的偏移量。 那么"跟手移动"的表达式应该是这样的:
(伪代码)
f(x) = x
f(y) = y
另外,我们最终希望改变的是 "某视图"的 translateX
和 translateY
属性,那么我们同时再把"期望改变的属性"和"某视图"的引用(anchor)也传递给 Native
,像这样:
(伪代码)
{
anchor: foo_view.ref // ----> 这是"产生手势的视图"的引用
props:
[
{
element: foo_view.ref, // ----> 这是"期望改变的视图"的引用
expression: f(x) = x, // ----> 这是具体的表达式
property: translateX // ----> 这是期望改变的属性
},
{
element: foo_view.ref,
expression: f(y) = y, // ----> y 属性
property: translateY
}
]
}
您可能注意到在上面这段伪代码中,"视图的引用"
(foo_view.ref)被传递了两次,这是因为 "产生手势的视图" 和 "期望改变的视图" 并不总是同一个。
比如说,你希望在视图A上滑动,而改变的是视图B的位置。因此,我们进行了区分,这样会更加通用。另外,正如上面说的,我们通过一个数据集合(即 element
、 expression
、 property
)来描述 "视图" 的行为。因为手势可能会伴随着视图多个属性的变换,所以props
中可以传递一个数组。
当这份配置传递到 Native
后, Native
会对目标视图( anchor
)设置手势事件监听器。当手指在屏幕上移动时,监听器会收到回调事件。紧接着, Native
会直接根据其内置的 表达式解析引擎
去解释执行表达式,并根据表达式执行的结果驱动视图变换。比如手势横向滑动 60px
纵向滑动 70px
,那么表达式 f(x)=x
和 f(y)=y
的结果分别是 60 和 70,再根据对应的属性 translateX
和 translateY
就可以对视图进行平移了。
整个过程不再需要和 JS
端进行通信,因此用户体验如丝般顺滑。
以上就是这套方案的基本原理。一句话总结:
BindingX 的核心思想就是将"交互行为"以表达式的方式描述,并提前预置到Native,避免在行为触发时JS与native的频繁通信。
事实上, BindingX
解决的不仅仅是手势交互问题,理论上任何 "频繁通信+UI更新" 的场景都可以使用这套方案。 BindingX
目前支持已经以下四种场景:
监听
pan
手势,更新UI。监听滚动容器(如List)的onscroll事件,更新UI。
监听设备传感器方向变化,更新UI。
动画。(即监听设备的每一帧的屏幕刷新回调事件,更新UI)。
平台支持
BindingX
通过插件的形式同时支持React Native和weex。在weex上,可以作为一个 weex 模块 直接注册到weex环境中, 在 JS
层同时支持 weex DSL 和 Rax DSL 。在 React Native
上,也可以以类似的方式使用。