一、描述

rax 内置组件 rax-waterfall 是 rax 为了更加方便的构建瀑布流方案而实现的内置组件。

使用 rax-waterfall 需要 weex 版本 v0.11.0+

使用的时候,建议单独安装,目前 rax 已经不建议安装 rax-components 了,使用哪个组件就单独安装依赖,更能够减少 bundle 的大小。

官方文档地址:

一些涉及官方文档的参数说明不再重复了。

二、源码

1、依赖

rax-waterfall 使用了下面这些依赖:

import {isWeex} from 'universal-env';
import View from 'rax-view';
import ScrollView from 'rax-scrollview';
import RefreshControl from 'rax-refreshcontrol';

2、web 环境的渲染方式

ScrollView 组件一样,针对 web 和 weex 平台, 采用了不同的渲染方式,在 web 平台上,rax 构建了无状态的 WebFall 组件, 用来渲染在 web 容器下的单个 item。

class WebFall extends PureComponent {
  calcHeightSum = (arr) => {
    let sum = 0;
    arr && arr.forEach(item => {
      sum += item;
    });
    return sum;
  };

  render() {
    const {renderItem = () => {}, dataSource, columnCount = 1} = this.props;
    let columns = [];
    let moduleHeights = [];

    for (let i = 0; i < columnCount; i++) {
      columns[i] = [];
      moduleHeights[i] = 0;
    }

    dataSource && dataSource.forEach((item, i) => {
      let targetColumnIndex = 0;
      let minHeight = moduleHeights[0];

      for (let j = 0; j < columnCount; j++) {
        if (moduleHeights[j] < minHeight) {
          minHeight = moduleHeights[j];
          targetColumnIndex = j;
        }
      }

      moduleHeights[targetColumnIndex] += item.height;
      columns[targetColumnIndex].push(item);
    });

    return (<View style={styles.waterfallWrap}>
      {columns.map((column, index) => {
        return (<View key={'column' + index} style={styles.waterfallColumn}>
          {column.map((item, j) => {
            return renderItem(item, 'c_' + index + j);
          })}
        </View>);
      })}
    </View>);
  }
}

如果在方法 renderItem(item,index) 中,将 index 直接渲染出来,会发现在 weex 容器和 web容器中渲染出来的 index 是不一样的, 在上面的代码中可以看出来,在 web 平台中,传递 index ,实际上传递的是 'c_' + index + j

这个设计目前没有发现理由,核心的 return 部分如下:

return (<View style={styles.waterfallWrap}>
      {columns.map((column, index) => {
        return (<View key={'column' + index} style={styles.waterfallColumn}>
          {column.map((item, j) => {
            return renderItem(item, 'c_' + index + j);
          })}
        </View>);
      })}

但是,看源码能够发现,在 web 环境中,rax-waterfall 最终竟然最终输出的是 <ScrollView> 组件,那在结合 <ScrollView> 组件在 web 环境的渲染结果,最终还是渲染了 <View>

因此,最终在 web 平台渲染的代码如下,其中 cells<WebFall> 组件的组合。

return (<ScrollView {...props} ref="scrollview">
        {cells}
    </ScrollView>);

3、weex 容器环境的渲染方式

如果是 weex 容器,则直接使用了 weex 的内置组件 waterfall

由于渲染方式的不同,对于数据的处理和内部组件的输出也是不同的,weex 容器环境因为使用了 waterfall,所以子组件是 <cell>,而 web 环境下,则是自定义的无状态组件 <WebFall>

    if (isWeex) {
      dataSource && dataSource.forEach((item, index) => {
        cells.push(<cell {...cellProps}>{renderItem(item, index)}</cell>);
      });
    } else {
      cells = cells.concat(<WebFall {...props} />);
    }

因此 weex 容器环境的渲染代码如下(其实整个 rax-waterfall 的设计还是集合了 waterfall):

 if (isWeex) {
      return (<waterfall
        style={{width: 750}}
        {...props}
        onLoadmore={props.onEndReached}
        loadmoreoffset={props.onEndReachedThreshold}
        loadmoreretry={this.state.loadmoreretry}
      >
        {cells}
      </waterfall>);
    }

4、不同容器环境下 header 组件的渲染方式

因为 web 和 weex 的整体渲染方式的不同,所以 header 的渲染方式也不相同。

rax-waterfall 的代码中,自定义了一个 Header 无状态组件,代码如下:

class Header extends PureComponent {
  render() {
    if (isWeex) {
      return <header {...this.props} append="tree" />;
    } else {
      return <View {...this.props} />;
    }
  }
}

三、使用 rax-waterfall 实现无限瀑布流

1、实现效果

期望效果:

3333.gif

上面动图是在 web 平台上截取的,可以发现本来 index 是一个数字 ,如 141 ,但是渲染出来的是 c_141,这就是在 web 环境下,对于 renderItem(item,index) 中 index 参数的处理。

1、state 的设计

瀑布流的重点就是,图片的高度是不一样的,因此一般返回的数据中,都有高度这个值。

  state = {
    list:[
      {height:300,item:{}},
      {height:200,item:{}},
      {height:100,item:{}},
      {height:150,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
      {height:300,item:{}},
      {height:200,item:{}},
      {height:100,item:{}},
      {height:150,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
      {height:300,item:{}},
      {height:200,item:{}},
      {height:100,item:{}},
      {height:150,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
    ]
  }

2、renderItem 方法

  renderItem = (item, index) => {
    return (
      <View style={[styles.item,{height:item.height}]}>
        <Text>Item {index}</Text>
      </View>
    )
  }

3、App.js

完整的组件代码

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

import styles from './App.css';

class App extends Component {
  state = {
    list:[
      {height:300,item:{}},
      {height:200,item:{}},
      {height:100,item:{}},
      {height:150,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
      {height:300,item:{}},
      {height:200,item:{}},
      {height:100,item:{}},
      {height:150,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
      {height:300,item:{}},
      {height:200,item:{}},
      {height:100,item:{}},
      {height:150,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
    ]
  }
  renderHeader = () =>{
    return (
      <View>
        <Text style={styles.header}>WaterFall Header </Text>
      </View>
    );
  }
  rednerFooter = () => {
    return (
      <View>
        <Text style={styles.header}>WaterFall Footer</Text>
      </View>
    )
  }
  renderItem = (item, index) => {
    return (
      <View style={[styles.item,{height:item.height}]}>
        <Text>Item {index}</Text>
      </View>
    )
  }
  onEndReached = () => {
    const tmp = [
      {height:100,item:{}},
      {height:500,item:{}},
      {height:200,item:{}},
      {height:450,item:{}},
      {height:250,item:{}},
      {height:350,item:{}},
    ];
    this.setState({
      list:this.state.list.concat(tmp)
    });
  }
  render() {
    return (
      <View style={styles.app}>
        <WaterFall 
          style={styles.waterfall}
          dataSource = {this.state.list}
          renderItem = {this.renderItem}
          renderHeader = {this.renderHeader}
          renderFooter = {this.rednerFooter}
          onEndReached = {this.onEndReached}
          columnCount = {3}
          columnWidth = {200}
        ></WaterFall>
      </View>
    );
  }
}

export default App;

4、完整的 css 代码

.app{
  width:750;
}
.header{
  text-align: center;
  width:750;
  height:50;
  background-color: #cccccc;
}
.item{
  justify-content: center; 
  align-items: center;
  margin-bottom: 10;
  width:200;
  background-color: #dfdfdf;
}
.waterfall{
  flex-direction: row;
  height:1333;
}