起因

最近做项目时,碰到了一个需求:客户需要修改列表滑动条的颜色。既然存在滑动条,肯定就是ScrollView、FlatList这种长列表。于是去官网(需科学上网)找了下相应的API。发现并没有比较友好的API支持滑动条的颜色修改。唯一支持的indicatorStyle属性,也仅实用IOS,且也无法实用自定义的颜色~~没办法,就只能自己动手做了。

分析

既然要自己动手重新做一个滑动条,那么我们可以先来参考下原有的滑动条效果。
这里我们先模拟200条数据。放到FlatList中。

componentDidMount() {
    const { data } = this.state;
    for (let i = 0; i < 200; i += 1) {
      data.push(`Data ${i}`);
    }
    ...
}

render(){
    ...
  <FlatList
      data={data}
      keyExtractor={item => (item)}
      initialNumToRender={9}
      renderItem={({ item }) => (
      <Text style={styles.item} >
      {item}
      </Text>)}
   />
}
模拟200条数据
滑动效果

从滑动效果图中可以看出:

  • 列表静止状态时,右边的滑动条是隐藏的。
  • 右边滑动条随着列表进行移动。
  • 从移动状态变为静止状态时,滑动条有一个过渡消失动画。

第1点直接将滑动条默认设置为隐藏就好,第3点添加一个基本的透明动画即可。
这里需要重点考虑第2点。
先定义一下基本观念:组件高度、内容真实高度、滑动条高度、滑动条可滑动的高度。如图:

hua-dong-tiao-de-huan-suan-guan-xi

从正常交互而言,右边滑动条滑动到一半时,左边组件也会展示整体内容的一半。右边滑动条滑动到底部时,左边组件也会展示完全部整体内容。所以:contain Height/content Height=indicetor Height/slider Height。sliderHeight的高度就是组件的高度。于是我们可推出indicatorHeight:indicatorHeight=containHeight^2 / sliderHeight。滑动条的滑动距离与上述类似:列表滑动距离/contentHeight=滑动条滑动距离/sliderHeight。而我们列表滑动距离是已知的。所以滑动条的滑动距离就应该是:滑动条滑动距离=列表滑动距离*containHeight/contentHeight。

render() {
      const { data, extraData, keyExtractor, renderItem, style, indicatorStyle } = this.props;
      const { indicator, contentHeight, containHeight } = this.state;
      const indicatorHeight = contentHeight > containHeight ? containHeight * containHeight / contentHeight : 0;
      return (
        <View style={{ flex: 1, flexDirection: 'row' }}>
          <FlatList
            onContentSizeChange={(width, height) => {
              // 获取内容真实高度
              this.setState({ contentHeight: height });
            }}
            onLayout={({ nativeEvent: { layout: { height } } }) => {
              // 获取当前组件高度
              this.setState({ containHeight: height });
            }}
            // 通过列表的滑动事件,得到具体的滑动值
            onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: indicator } } }])}
            ...
          />

          <Animated.View style={[
            styles.indicator, {
              ...,
              height: indicatorHeight,
              transform: [{ translateY: Animated.multiply(indicator, containHeight / contentHeight ) }]
            }]}
          />
        </View>
      );
修改后的代码
滑动条可移动了

滑动条是可进行移动了,但是还默认隐藏,以及停止滑动的消失过渡动画。我们将其补全加上去。最终代码如下:

constructor(props) {
      super(props);
      this.state = {
        indicator: new Animated.Value(0), // 滑动移动的距离
        opacity: new Animated.Value(0), // 默认隐藏
        contentHeight: 1, // 内容真实高度
        containHeight: 0// 组件可见高度
      };
    }

    startAnimation=toValue => {
      Animated.timing(
        this.state.opacity,
        {
          toValue,
          duration: 1000,
        }
      ).start();
    }


    render() {
      const { data, extraData, keyExtractor, renderItem, style, indicatorStyle } = this.props;
      const { indicator, contentHeight, containHeight, opacity } = this.state;
      const indicatorHeight = contentHeight > containHeight ? containHeight * containHeight / contentHeight : 0;
      return (
        <View style={{ flex: 1, flexDirection: 'row' }}>
          <FlatList
            onContentSizeChange={(width, height) => {
              // 获取内容真实高度
              this.setState({ contentHeight: height });
            }}
            onLayout={({ nativeEvent: { layout: { height } } }) => {
              // 获取当前组件高度
              this.setState({ containHeight: height });
            }}
            // 通过列表的滑动事件,将滑动偏移量进行一个映射
            onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: indicator } } }])}
            // 滑动触摸结束时隐藏
            onMomentumScrollEnd={() => this.startAnimation(0, 1000)}
            // 滑动触摸开始时展示
            onScrollBeginDrag={() => this.startAnimation(1)}
            ...
          />

          <Animated.View style={[
            styles.indicator, {
              ...
              opacity,
              height: indicatorHeight,
              transform: [{
                translateY: Animated.multiply(indicator, containHeight / contentHeight)
              }]
            }]}
          />
        </View>
      );
    }
最终效果

至此,效果已经全部ok~~~