组件思考-ScrollView

270 阅读2分钟

高速轮播的snap(2022-09-14)

ScrollView里有一对很有意思很酷炫的属性

snapToInterval
snapToAlignment

这两个属性的具体解释,请参考 snapToIntervalsnapToAlignment

以下面这段代码为例(原始代码来自这里)

import React, { useRef } from "react";
import {
  SafeAreaView,
  ScrollView,
  Text,
  StyleSheet,
  View,
  ImageBackground,
  Animated,
  useWindowDimensions
} from "react-native";

const images = new Array(6).fill('https://images.unsplash.com/photo-1556740749-887f6717d7e4');

const App = () => {
  const scrollX = useRef(new Animated.Value(0)).current;

  const { width: windowWidth } = useWindowDimensions();

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.scrollContainer}>
        <ScrollView
          horizontal={true}
          pagingEnabled
          showsHorizontalScrollIndicator={false}
          onScroll={Animated.event([
            {
              nativeEvent: {
                contentOffset: {
                  x: scrollX
                }
              }
            }
          ])}
          snapToInterval={windowWidth}
          snapToAlignment='center'
          scrollEventThrottle={1}
        >
          {images.map((image, imageIndex) => {
            return (
              <View
                style={{ width: windowWidth, height: 250 }}
                key={imageIndex}
              >
                <ImageBackground source={{ uri: image }} style={styles.card}>
                  <View style={styles.textContainer}>
                    <Text style={styles.infoText}>
                      {"Image - " + imageIndex}
                    </Text>
                  </View>
                </ImageBackground>
              </View>
            );
          })}
        </ScrollView>
        <View style={styles.indicatorContainer}>
          {images.map((image, imageIndex) => {
            const width = scrollX.interpolate({
              inputRange: [
                windowWidth * (imageIndex - 1),
                windowWidth * imageIndex,
                windowWidth * (imageIndex + 1)
              ],
              outputRange: [8, 16, 8],
              extrapolate: "clamp"
            });
            return (
              <Animated.View
                key={imageIndex}
                style={[styles.normalDot, { width }]}
              />
            );
          })}
        </View>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  scrollContainer: {
    height: 300,
    alignItems: "center",
    justifyContent: "center"
  },
  card: {
    flex: 1,
    marginVertical: 4,
    marginHorizontal: 16,
    borderRadius: 5,
    overflow: "hidden",
    alignItems: "center",
    justifyContent: "center"
  },
  textContainer: {
    backgroundColor: "rgba(0,0,0, 0.7)",
    paddingHorizontal: 24,
    paddingVertical: 8,
    borderRadius: 5
  },
  infoText: {
    color: "white",
    fontSize: 16,
    fontWeight: "bold"
  },
  normalDot: {
    height: 8,
    width: 8,
    borderRadius: 4,
    backgroundColor: "silver",
    marginHorizontal: 4
  },
  indicatorContainer: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center"
  }
});

export default App;

当我在手机里运行时,我会看到一个6个图片的轮播图,每次手指左右滑动一下,不论速度是快还是慢,图片都是一张一张切换。当我把图片数量扩大10倍,变成60,再一张一张切换,就会觉得要过很久才能切换到最后一张图片。

const images = new Array(60).fill('https://images.unsplash.com/photo-1556740749-887f6717d7e4');
慢速滚动

注:当你修改为60时,这段代码涉及到的动画部分的一些功能,就无法正常运行,但是这里只是为了说明sanp的那两个属性的效果,请忽略动画部分。

此时,我的记忆中出现了之前遇到的一些类似场景,在那些场景下,当我用手飞速滑动轮播图时,图片飞快的滚动起来,瞬间抵达非常靠后的一张图片。但是此时,不论我怎么滑动我的手指,轮播图还是不紧不慢的一张一张的翻动,完全不考虑到有60张图片在后面排队。

于是我加上了这两个属性

... ...
const { width: windowWidth } = useWindowDimensions();
... ...
<ScrollView
... ...
    snapToInterval={windowWidth}
    snapToAlignment='center'
... ...
>

一瞬间,轮播图飞速旋转起来啦,并且不会错位,每次都是正好是一图片居中,这就是这两个参数一起作用下的神奇效果。

快速滚动

这两个参数都是一起出现的,至少我查阅了git hub上近150个React Native的项目,只要是用到其中某一个属性的,必然伴随着另一个。