一、描述

在 bindingX 官网上看到一个效果,主要是实现类似视差滚动的效果以及渐变的tab,地址如下:

这个示例对于页面效果来说无疑有很大的视觉提升,对于实现上来讲,虽然也可以频繁 setState 去实现滚动,但是会存在很大的性能问题。

而 rax 的 universal-transition 其实并不是很实用,因为这不是周期或者时间上的动画,而是需要监听 scroll 去做到。

因此,如果不使用 findDomNode(ref).style.property = x 这种方式(从来没在 weex 中用过这种方式,不确定 weex 是否真的支持,但至少是绝对不推荐使用的, 即使在 web 环境上也是不推荐的),使用 bindingX 去实现无疑是最好的。

我的实现并没有参照上面 playground 中的实现方法,因为他的代码有点太多了。

为了简化实现方式也简化代码,我去掉了顶部的 tabbar,并且使用一个 appbar 去实现一个渐变出现的 binding,这个 appbar 当中可能会出现诸如导航菜单之类的操作,或者是标题。

简化之后的 rax 组件及其他库包括:

  • rax-listview:用于列表滚动
  • rax-view
  • rax-text
  • universal-env
  • weex-bindingx

最终实现的效果先抛出来:

因为在浏览器上录制的 gif,并且由于web容器的一些限制,其实有的东西没有表现出来,具体的可以使用 bindingX 的 APP 扫 jsplayground 的二维码在 iphone 上的一些特殊效果。

GIF.gif

二、实现前

写代码之前,首先分析一下页面的主要组成:

1、顶部红色固定背景

第一个是顶部的红色区块,他并不是 ListView 的一部分,而是作为 position: fixed 的元素部在整个页面容器的顶部位置。

不放在 ListView 里面的原因一个是布局不是很好去布局,还有一个是在 iphone 上,ListView 这种可滚动的容器其实是可以一直往下拉的,(上面的 gif 图因为是在 web 上录制,没有办法表现出来),而 web 容器和 android 容器则不会有这种问题,这是系统层面的,因此 weex 并没有对其做兼容处理,而是遵循了系统的规范。

因为在 iphone 上可以一直往下拉,因此不能将红色背景放在滚动容器的 header 中,因为我们期望的是 容器在滚动的时候,往上面滚动,红色背景网上移动,往下面滚动,红色背景下来全部显示后,就不在跟随往下,然后在 iphone 上 listview 可以该往下就往下(最好需要iphone扫码试试效果)

2、滚动列表 ListView

滚动列表对每个 row 的渲染其实没什么复杂的东西,不在这里重复。这里着重说一下 ListView 距离顶部的位置,见下面图:

微信截图_20181029004201.png

这个顶部的距离一开始的实现我想当然的是使用 margin 去实现的,结果发现,因为 listView 使用了 margin,导致顶部的这个距离是一直存在的,根本滚动不上去。

asda.gif

使用 marginTop 会导致顶部总是会有空隙存在,marginTop 是不会上去的。

因此如果要在顶上做一个空白区域,需要在 listView 内部解决,我目前的做法是在 renderHeader 中渲染了一个高度为 150 的 View 容器(高度由最终需要的视觉去决定),这样会使得 ListView 整体高度还是页面容器的高度,然后内容列表还是正常的渲染,避免了上面的问题。

3、一个 appbar

这里的 appbar 其实并不是系统原生的, 而是自己绘制的。

appbar 也是 position: fixed 在顶上的,并且需要在 红色背景区块的后面,以便于能够盖到所有内容之上。

比较关键的点是,初始化的 opacity 必须是 0 ,以为在 binding 的过程中,是需要从 0 开始变化。

三、实现布局

1、红色固定背景区块

由于代码太多,整理的代码框架就不在文章里面体现,完整的代码最终会给出 jsplayground

JSX:

<View style={styles.topHeader} ref="topHeader" />

CSS:

.topHeader {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  width: 750;
  height: 250;
  border-bottom-left-radius: 20;
  border-bottom-right-radius: 20;
  background-color: red;
}

2、ListView 列表容器数据渲染部分

JSX:

renderRow = (item, index) => {
    return (
      <View style={styles.itemWrapper}>
        <View style={styles.item}>
          <Text style={styles.text}>{item} - {index}</Text>
        </View>
      </View>
    );
  }

<ListView 
    style={styles.list}
    ref="listview"
    renderHeader={this.renderHeader}
    dataSource={list}
    renderRow={this.renderRow}
  />

CSS:

.list {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin-top: 150;
}

.itemWrapper {
  width: 750;
  flex-direction: row;
  justify-content: center;
  align-items: center;
}
.item {
  width: 600;
  height: 200;
  margin-bottom: 30;
  background-color: #cccccc;
  flex-direction: row;
  justify-content: center;
  align-items: center;
}

.text {
  font-size: 30;
  color: red;
}

3、ListView 列表容器顶部占位空白区块

JSX:

renderHeader = () => {
    return (
      <View style={styles.header} />
    );
  }

CSS:

.header {
  width: 750;
  height: 150;
}

4、appbar 部分

JSX:

 <View style={styles.appBar} ref="appBar" />

css:

.appBar {
  position: fixed;
  top:0;
  left:0;
  width: 750;
  height: 128;
  opacity: 0;
  background-color: #000000;
}

四、binding

前面再多也是铺垫,最核心的还是 binding,整体的 binding 代码如下:

 binding = () => {
    const listView = getEl(this.refs.listview);
    Binding.bind({
      anchor: listView,
      eventType: 'scroll',
      props: [
        {
          element: getEl(this.refs.topHeader),
          property: 'transform.translateY',
          expression: {
            origin: 'y < 0 ? 0 : 0-(y/2)'
          }
        },
        {
          element: getEl(this.refs.appBar),
          property: 'opacity',
          expression: {
            origin: 'y/500'
          }
        }
      ]
    });
  }

同样的, 为了兼容 web 和 weex 容器的节点获取,需要使用下面方法:

function getEl(el) {
  return isWeex ? findDOMNode(el).ref : findDOMNode(el);
}

binding 其实需要绑定两个节点的属性变化,而 binding 作用的节点是作用在 scroll 上,anchor 自然是 listview。

绑定的两个节点作用在顶部红色背景区块上,因为需要跟随 listview 往上滚动,属性为 transform.translateY

我们只需要往上滚动,而不需要一起往下滚动,并且为了实现时差滚动的效果,当 listview 往上滚动的距离和红色背景区块滚动的距离不能一样。

y < 0 ? 0 : 0-(y/2) 当中,yeventType=scroll 中表示容器的绝对偏移量,而不是容器滚动的距离,这个和 dy 是有区别的,如果 y > 0 表示容器 y 轴绝对便宜量,也就是容器向下的偏移,而 y < 0 则表示容器向上的偏移量。因此表达式中只有当 y<0 的时候才进行属性变动,并且变动速度是容器速度的 1/2

对于 appbar 的 opacity 属性的变化,就更好说了,无非就是从 0 -1 最简单的方法就是使用 y/500 来是的 appbar 随着滚动而变化。

五、代码

jsplayground 放了一份代码,使用 bindingx 扫二维码能够在 iphone 上看到不同的效果。

六、iphone 上的效果

gif5新文件.gif