gpt4 book ai didi

Creating an animated, swipable image stack in React Native(在Reaction Native中创建动画、可滑动的图像堆栈)

转载 作者:bug小助手 更新时间:2023-10-24 17:54:31 35 4
gpt4 key购买 nike



I'm trying to create an interaction in React Native where the user can swipe through a stack of images. The images should animate so that on each swipe, the top card goes to the bottom of the stack, and every other card shifts forward. It should look similar to this:

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


Card deck animation


In my usecase, the length of the image array is arbitrary, but only 3 images should be visible at once. The user should be able to loop through all of the images by continuously swiping.

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


I'm using react-native-reanimated 3 and react-native-gesture-handler. I've tried several different approaches, all of which come close but have various issues. Either the animation runs but the images flicker when I update the stack, or the images cycle correctly but don't animate at the end of the gesture (which is what happens with my current approach).

我使用的是REACTIVE-NERNAL-REACTIVATED 3和REACTION-NERNAL-GRIPTION-HANDLER。我尝试了几种不同的方法,所有方法都很接近,但都有不同的问题。当我更新堆栈时,要么动画运行但图像闪烁,要么图像正确循环但在手势结束时没有动画(这就是我目前的方法所发生的情况)。


Can anyone put me on the right track here?

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


EDIT: I think I solved it. I was passing a key prop to a component higher up in the tree which was causing unnecessary re-renders, and my code had some needless complexity that was messing up the card state.

编辑:我想我解决了这个问题。我将一个关键道具传递给树中更高的一个组件,该组件会导致不必要的重新渲染,而我的代码有一些不必要的复杂性,这会扰乱卡片状态。


Video here of it working as intended.

这里是它按预期工作的视频。


Here is the code, with some irrelevant parts elided:

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



// 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)


更多回答

Very impressive question.

非常令人印象深刻的问题。

优秀答案推荐

If the image component is reloading when the image is same, have you checked if the source props for the image is memoized. It will be easier to debug this with actual code, can you share it to better explain your implementation.

如果图像组件在图像相同时重新加载,您有没有检查图像的源道具是否已备注。用实际代码调试它会更容易,你能分享它来更好地解释你的实现吗?



To really cut to the heart of it, the solution was essentially about this:

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


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],
]

The styleMap contains the style values for each card at each step of the interaction, and the imageStates matrix maps the cards to the styleMap.

Style Map包含交互的每个步骤中每个卡片的样式值,ImageStates矩阵将卡片映射到style Map。


更多回答

I've added the code. Calling the dispatchNewState function results in a re-render, even though the UI doesn't meaningfully change.

我已经添加了代码。调用DispatchNewState函数会导致重新呈现,即使UI没有有意义的更改。

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

您的回答可以通过补充支持信息进行改进。请编辑以添加更多细节,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以在帮助中心找到更多关于如何写好答案的信息。

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com