我正试图在Reaction Native中创建一个交互,用户可以在其中滑动一堆图像.图像应该是动画的,这样每次刷卡时,顶部的卡都会移到堆叠的底部,其他卡都会向前移动.它应该类似于下面的内容:

Card deck animation

在我的用例中,图像数组的长度是任意的,但一次只能看到3张图像.用户应该能够通过连续滑动循环通过所有的图像.

我用的是react-native-reanimated 3和react-native-gesture-handler.我try 了几种不同的方法,所有方法都很接近,但都有不同的问题.当我更新堆栈时,要么动画运行但图像闪烁,要么图像正确循环但在手势结束时没有动画(这就是我目前的方法所发生的情况).

有人能把我带到正确的轨道上来吗?

我想我解出来了.我将keyprops 传递给树中更高的组件,这会导致不必要的重新渲染,而我的代码有一些不必要的复杂性,这会扰乱卡片状态.

其中Video here个都能达到预期效果.

以下是代码,省略了一些不相关的部分:


// day-card.js

const CARD_MARGIN = 20
const CARD_OFFSET = 50

function DayCard({
    selectedId,
    dateString,
    creationTime,
    isShowingStack,
    toggleStack,
    allImages,
}) {
    const dispatch = useDispatch()
    const insets = useSafeAreaInsets()
    const numImages = allImages.length

    const [imageStack, setImageStack] = useState(allImages)

    const windowWidth = Dimensions.get('window').width

    const topCardTranslateX = useSharedValue(0)
    const topCardTranslateY = useSharedValue(0)
    const bottomCardTranslateX = useSharedValue(0)
    const bottomCardTranslateY = useSharedValue(0)
    const middleCardTranslateX = useSharedValue(0)
    const middleCardTranslateY = useSharedValue(0)

    const topCardMargin = useSharedValue(CARD_MARGIN)
    const middleCardMargin = useSharedValue(CARD_MARGIN)
    const bottomCardMargin = useSharedValue(CARD_MARGIN)

    const topCardZIndex = useSharedValue(4)
    const middleCardZIndex = useSharedValue(3)
    const bottomCardZIndex = useSharedValue(2)

    const topCardOpacity = useSharedValue(1)
    const middleCardOpacity = useSharedValue(1)
    const bottomCardOpacity = useSharedValue(1)

    const middleCardRotation = useSharedValue(0)
    const bottomCardRotation = useSharedValue(0)
    const topCardRotation = useSharedValue(0)

    const topCardBrightness = useSharedValue(1)
    const middleCardBrightness = useSharedValue(0.75)
    const bottomCardBrightness = useSharedValue(0.5)

    const animationIdx = useSharedValue(0)

    const cardStyles = [
        useAnimatedStyle(() => ({
            transform: [
                {
                    translateX: topCardTranslateX.value,
                },
                {
                    translateY: topCardTranslateY.value,
                },
                {
                    rotate: `${topCardRotation.value}deg`,
                },
            ],
            width: windowWidth - topCardMargin.value * 2,
            marginHorizontal: topCardMargin.value,
            zIndex: topCardZIndex.value,
            opacity: topCardOpacity.value,
        })),

        useAnimatedStyle(() => ({
            transform: [
                {
                    translateX: middleCardTranslateX.value,
                },
                {
                    translateY: middleCardTranslateY.value,
                },
                {
                    rotate: `${middleCardRotation.value}deg`,
                },
            ],
            width: windowWidth - middleCardMargin.value * 2,
            marginHorizontal: middleCardMargin.value,
            zIndex: middleCardZIndex.value,
            opacity: middleCardOpacity.value,
        })),

        useAnimatedStyle(() => ({
            transform: [
                {
                    translateX: bottomCardTranslateX.value,
                },
                {
                    translateY: bottomCardTranslateY.value,
                },
                {
                    rotate: `${bottomCardRotation.value}deg`,
                },
            ],
            width: windowWidth - bottomCardMargin.value * 2,
            marginHorizontal: bottomCardMargin.value,
            zIndex: bottomCardZIndex.value,
            opacity: bottomCardOpacity.value,
        })),
    ]

    const imageIndices = [
        [0, 1, 2],
        [2, 0, 1],
        [1, 2, 0],
    ]

    const Cards = [
        <Animated.View style={[styles.card, cardStyles[0]]}>
            <SingleCard
                dateString={dateString}
                uri={imageStack[imageIndices[animationIdx.value][0]].uri}
                id={imageStack[imageIndices[animationIdx.value][0]].id}
                brightness={topCardBrightness}
            />
        </Animated.View>,
        numImages > 1 ? (
            <Animated.View style={[styles.card, cardStyles[1]]}>
                <SingleCard
                    uri={imageStack[imageIndices[animationIdx.value][1]].uri}
                    id={imageStack[imageIndices[animationIdx.value][1]].id}
                    dateString={dateString}
                    brightness={middleCardBrightness}
                />
            </Animated.View>
        ) : null,
        numImages > 2 ? (
            <Animated.View style={[styles.card, cardStyles[2]]}>
                <SingleCard
                    uri={imageStack[imageIndices[animationIdx.value][2]].uri}
                    id={imageStack[imageIndices[animationIdx.value][2]].id}
                    dateString={dateString}
                    brightness={bottomCardBrightness}
                />
            </Animated.View>
        ) : null,
    ]

    const dispatchNewState = () => {
        dispatch(selectImageForDate(imageStack[0]))
    }

    const handleToggle = () => {
        if (isShowingStack) {
            topCardMargin.value = withTiming(CARD_MARGIN)
            middleCardMargin.value = withTiming(CARD_MARGIN)
            bottomCardMargin.value = withTiming(CARD_MARGIN)

            middleCardTranslateY.value = withTiming(0)
            bottomCardTranslateY.value = withTiming(0)
            topCardTranslateY.value = withTiming(0)
            dispatchNewState()
        } else {
            setCardState()
        }
        toggleStack()
    }

    const DEFAULT_WIDTH = windowWidth - CARD_MARGIN * 2
    const MIDDLE_WIDTH = windowWidth - CARD_MARGIN

    const styleMap = {
        translateY: [CARD_OFFSET, CARD_OFFSET / 2, 0],
        brightness: [1, 0.75, 0.5],
        margin: [0, CARD_MARGIN / 2, CARD_MARGIN],
        width: [windowWidth, MIDDLE_WIDTH, DEFAULT_WIDTH],
        zIndex: [4, 3, 2],
    }

    const endGesture = () => {
        'worklet'
        topCardTranslateX.value = withSpring(0)
        topCardRotation.value = withTiming(0)
        topCardOpacity.value = withTiming(1)
        middleCardTranslateX.value = withSpring(0)
        middleCardRotation.value = withTiming(0)
        middleCardOpacity.value = withTiming(1)
        bottomCardTranslateX.value = withSpring(0)
        bottomCardRotation.value = withTiming(0)
        bottomCardOpacity.value = withTiming(1)
    }

    const setCardState = () => {
        'worklet'
        const topCardIdx = imageIndices[animationIdx.value][0]
        const middleCardIdx = imageIndices[animationIdx.value][1]
        const bottomCardIdx = imageIndices[animationIdx.value][2]

        topCardTranslateY.value = withSpring(styleMap.translateY[topCardIdx])
        middleCardTranslateY.value = withSpring(
            styleMap.translateY[middleCardIdx]
        )
        bottomCardTranslateY.value = withSpring(
            styleMap.translateY[bottomCardIdx]
        )

        topCardMargin.value = withSpring(styleMap.margin[topCardIdx])
        middleCardMargin.value = withSpring(styleMap.margin[middleCardIdx])
        bottomCardMargin.value = withSpring(styleMap.margin[bottomCardIdx])

        topCardZIndex.value = styleMap.zIndex[topCardIdx]
        middleCardZIndex.value = styleMap.zIndex[middleCardIdx]
        bottomCardZIndex.value = styleMap.zIndex[bottomCardIdx]

        middleCardBrightness.value = withTiming(
            styleMap.brightness[middleCardIdx]
        )
        topCardBrightness.value = withTiming(styleMap.brightness[topCardIdx])
        bottomCardBrightness.value = withTiming(
            styleMap.brightness[bottomCardIdx]
        )
    }

    const stackGesture = Gesture.Pan()
        .onChange(({ translationX }) => {
            const rotation = interpolate(
                translationX,
                [-windowWidth, windowWidth],
                [-45, 45]
            )
            const opacity = interpolate(
                Math.abs(translationX),
                [0, windowWidth],
                [1, 0.5]
            )
            if (animationIdx.value === 0) {
                topCardRotation.value = `${rotation}deg`
                topCardTranslateX.value = translationX
                topCardOpacity.value = opacity
            } else if (animationIdx.value === 1) {
                middleCardRotation.value = `${rotation}deg`
                middleCardTranslateX.value = translationX
                middleCardOpacity.value = opacity
            } else if (animationIdx.value === 2) {
                bottomCardRotation.value = `${rotation}deg`
                bottomCardTranslateX.value = translationX
                bottomCardOpacity.value = opacity
            }
        })
        .onEnd(() => {
            endGesture()
            if (
                (animationIdx.value === 0 &&
                    Math.abs(topCardTranslateX.value) > 150) ||
                (animationIdx.value === 1 &&
                    Math.abs(middleCardTranslateX.value) > 150) ||
                (animationIdx.value === 2 &&
                    Math.abs(bottomCardTranslateX.value) > 150)
            ) {
                animationIdx.value = (animationIdx.value + 1) % 3
                runOnJS(setImageStack)([...imageStack.slice(1), imageStack[0]])
                setCardState()
            }
        })

    return (
        <BlurView
            intensity={10}
            alignContent="center"
            justifyContent="center"
            flex={1}
            tint="dark"
        >
            <GestureDetector
                gesture={isShowingStack ? stackGesture : null}
            >
                <View>
                    {Cards[0]}
                    {Cards[1]}
                    {Cards[2]}
                </View>
            </GestureDetector>
        </BlurView>
    )
}

const styles = StyleSheet.create({
    card: {
        position: 'absolute',
        top: 0,
        width: '100%',
    },
})

export default memo(DayCard)

// single-card.js

function SingleCard({ uri, dateString, id, brightness }) {
    const animatedStyle = useAnimatedStyle(() =>
        brightness
            ? {
                  opacity: 1 - brightness.value,
              }
            : {}
    )
    return (
        <SharedElement id={id}>
            <ImageBackground
                style={styles.image}
                source={{ uri }}
                alt={`A photo taken on ${dateString}`}
            >
                {brightness && (
                    <Animated.View
                        style={[
                            {
                                backgroundColor: 'black',
                                flex: 1,
                            },
                            animatedStyle,
                        ]}
                    />
                )}
            </ImageBackground>
        </SharedElement>
    )
}

const styles = StyleSheet.create({
    image: {
        width: '100%',
        aspectRatio: 3 / 4,
        position: 'relative',
    },
})

export default memo(SingleCard)

推荐答案

要真正切入问题的核心,解决方案基本上是这样的:

const styleMap = {
        translateY: [CARD_OFFSET, CARD_OFFSET / 2, 0],
        brightness: [1, 0.75, 0.5],
        margin: [0, CARD_MARGIN / 2, CARD_MARGIN],
        width: [windowWidth, MIDDLE_WIDTH, DEFAULT_WIDTH],
        zIndex: [4, 3, 2],
    }

    const imageStates =
        numImages > 2
            ? [
                  [0, 1, 2],
                  [2, 0, 1],
                  [1, 2, 0],
              ]
            : [
                  [0, 1],
                  [1, 0],
              ]

styleMap包含交互每个步骤中每个卡片的样式值,imageStates矩阵将卡片映射到styleMap.

Javascript相关问答推荐

以逗号分隔的列表来数组并填充收件箱列表

React redux状态未在React-Router库中呈现

Angular 拦截器错误处理删除方法问题

自定义帖子类型帖子未显示在网站上

如何通过继承contentitable属性的元素捕捉keydown事件?

Cookie中未保存会话数据

Google图表时间轴—更改hAxis文本 colored颜色

通过嵌套模型对象进行Mongoose搜索

如何解决chrome—extension代码中的错误,它会实时覆盖google—meet的面部图像?'

为什么当我解析一个promise时,输出处于挂起状态?

如何在每次单击按钮时重新加载HighChart/设置HighChart动画?

这个值总是返回未定义的-Reaction

React.Development.js和未捕获的ReferenceError:未定义useState

使用js构造一个html<;ath&>元素并不能使其正确呈现

DOM不自动更新,尽管运行倒计时TS,JS

如何在Bootstrap中减少网格系统中单个div的宽度

JavaScript不重定向配置的PATH

TinyMCE 6导致Data:Image对象通过提供的脚本过度上载

在表单集中保存更改时删除';禁用';

JWT Cookie安全性