一、描述

遇到一个问题,在 rax 中布局使用的是 rax-recyclerview,其中布局示意如下。

12111GIF.gif

从上面的示意图中有以下几部分组成整个布局:

  • Recycler.Header:顶部的黑色内容部分,rax 样式的高度为 580
  • Recycler.Header: 顶部红色按钮,内容: 按钮点击,rax 样式的高度为 180
  • Recycler.Cell: 多个内容,rax 样式的高度为 130
  • Recycler.Header: 蓝色按钮,内容:二级header,rax 样式的高度为 180
  • Recycler.Cell: 多个内容,rax 样式高度为 130

现在有如下的需求:

如上面示意图中所示的部分,无论在 weex 容器环境还是在 web 容器环境,都要做到点击某个部分(如红色按钮),页面滚动到某个内容,比如(蓝色按钮)

这个需求看似简单,但是因为使用的是 RecyclerView,因此在实践的时候也发现了一些问题。

二、不可行方案

最早的解决方案想当然的使用蓝色按钮距离顶部的位置进行滚动,要获取DOM元素距离顶部的位置在 web 环境不用罗嗦了,方法很多,在 weex 环境可以使用 module dom

dom module 有个方法是 dom.getComponentRect 可以通过 ref 获取距离顶部的高度,比如下面的方式

const _this = this;
let dom = __weex_require__('@weex-module/dom');
  dom.getComponentRect(Ele, (info) => { 
    _this.setState({  
      title: JSON.stringify(info),
    });  
    this.refs.scrollView.scrollTo({y: info.size.top}); 
  });

上面方案不可行的原因在于, RecyclerView 会对 Node 进行回收和利用,因此是获取不到需要的 info.size.top 的,或者获取的是错误的 info.size.top,因此无法使用这个高度进行滚动

三、最终采取的方案

1、滚动的实现

1) weex 容器下的滚动

RecyclerView 本身是支持 scrollTo() 的,但是文档中没有说出来,这个看 RecyclerView 的源码就能够知晓, RecyclerView 组件本身定义了 scrollTo 方法,可以通过 ref 直接使用。

this.refs.RecyclerContainer.scrollTo({y: 100, x: 100});

这个方法支持传入一个对象,属性为 yx 表示坐标,具体的可以看 ListView 的文档:

2)web 容器下的滚动

这个就很简单,如果定义了 ref,则通过 rax 的 findDOMNode 拿到真实的 DOM 节点,然后使用 scrollTo(0,100) 方法实现 web 下的滚动。

findDOMNode(this.refs.RecyclerContainer).scrollTo(0, 1000);

2、滚动距离的获取

获取滚动距离前面已经说了,是没办法通过 dom.getComponentRect 获取的,因为 RecyclerView 的回收利用机制。

既然这个方法走不通,就需要使用折中的方式。

在最开始的时候我列出了几个部分的高度,目的就在这里,既然没办法通过 dom 获取高度,则需要自己去拼接高度。

getTop = () => {
  const {length} = this.state;
  const top =  580 + length * 130 + 180;
  this.setState({   
    top
  });  
  return top;
}

length 是整个 list 的长度,因此高度是 this.state.length * 130,最终调用 scrollTo 的时候,使用的是 this.state.top

3、滚动高度的额外处理

如果是 weex 环境,使用 getTop() 方法获取的高度可以直接使用,但是在 web 环境则不行,需要进行计算。

rax 中样式的高度和宽度都是不带单位的,因为会进行计算,因此在 web 环境下,要滚动 getTop() 的距离,也需要进行计算。

关于 rax 的比例计算,可以参考:

计算的结果是:

let proper = screen.width / 750;
findDOMNode(this.refs.scrollView).scrollTo(0, this.state.top * proper);

三、示例

1、jsx

import {createElement, Component, findDOMNode} from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import RecyclerView from 'rax-recyclerview';
import Touchable from 'rax-touchable';
import BindingX  from 'weex-bindingx';
import  {isWeex, isWeb} from 'universal-env';
import styles from './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      length: 20,
      title:'header',
      top:0
    }
    this.renderData = this.renderData.bind(this);
  }
getTop = () => {
  const {length} = this.state;
  const top =  580 + length * 130 + 180;
  this.setState({   
    top
  });  
  return top;
}
  componentDidMount() {
    this.getTop();
  }
  scrollHandle = () => {
    const _this = this;
    const Ele = findDOMNode(this.refs.headerFirst); 
    if (isWeex) {
      this.refs.scrollView.scrollTo({y: this.getTop()}); 
    } else {
      let proper = screen.width / 750;
      findDOMNode(this.refs.scrollView).scrollTo(0, this.state.top * proper);
    }
  }
  renderData() { 
    const arr = [];  
    for (let i=0;i<this.state.length;i++) {
      arr.push(
        <RecyclerView.Cell>
          <View style={styles.cell}>
            <Text style={styles.cellText}>{i}</Text>
          </View>
        </RecyclerView.Cell>  
      );
    }
    return arr;
  }
  render() { 
    return (
      <View style={styles.app} ref="app" >
        <RecyclerView ref="scrollView">
          <RecyclerView.Header>
            <Text style={styles.header}>{this.state.top}</Text>  
          </RecyclerView.Header>  
          <RecyclerView.Header>
            <Touchable 
            onPress = { this.scrollHandle}
            style={styles.headerBtn}
            ref="headerFirst"  
          >按钮点击</Touchable>   
          </RecyclerView.Header>  
          {this.renderData()}
            
          <RecyclerView.Cell >
            <Touchable style={styles.secondBtn} 
            ref="headerSecond" 
          >二级header</Touchable>  
          </RecyclerView.Cell>  
          {this.renderData()}
        </RecyclerView>
      </View>
    );
  }
}

export default App;

2、css

.app {
  flex: 1;
  /* justify-content: center; */
  /* align-items: center; */
}

.appHeader {
  background-color: #222;
  width: 160;
  height: 260;
  padding: 40;
}

.header{
  width: 750;
  height:580;
  background-color: #000000;
  color: #fff;
}
.headerBtn{
  width: 750;
  height:180;
  background-color: red;
  color: #fff;
  text-align: center;
}
.cell{
  background-color: #ccc;
  width: 750;
  height: 100;
  margin-bottom: 30;
  align-items: center;
  justify-content: center;
}

.cellText{
  color: #000000;
  font-size: 30;
}

.secondBtn{
  width: 750;
  height:180;
  background-color: blue;
  color: #fff;
  text-align: center;
}

.viewWrapper{
  width: 750;
}