Skip to content

Commit

Permalink
fix: few changes for converflow ui/ux improvement
Browse files Browse the repository at this point in the history
ZhenQian2018 committed Jan 24, 2025
1 parent f7f3730 commit 6d57645
Showing 10 changed files with 343 additions and 220 deletions.
3 changes: 2 additions & 1 deletion src/components/CoverFlow/Detail/index.tsx
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
borderRadius: 10,
borderRadius: 20,
},
song: {
padding: 15,
@@ -43,6 +43,7 @@ const Detail = ({ id }: { id: number }) => {
<TouchableWithoutFeedback
onPress={(e) => {
e.stopPropagation()
e.preventDefault()
}}
>
<Animated.View style={{ ...styles.item, width: SCREEN_WIDTH * 0.4, height: SCREEN_HEIGHT }}>
54 changes: 33 additions & 21 deletions src/components/CoverFlow/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
import { useCoverflowStore } from '@/store/coverflow'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Animated, StyleSheet, TouchableWithoutFeedback, View } from 'react-native'
import Detail from './Detail'

@@ -47,7 +48,7 @@ const Item = (props: ItemProps) => {
const [flipAnimation] = useState(new Animated.Value(0))
const [flipAnimationForScale] = useState(new Animated.Value(0))
const animatedY = useRef(new Animated.Value(0)).current // 初始高度

const { toggleDetail, togleDetail } = useCoverflowStore()
const flipRotateY = useRef(
flipAnimation.interpolate({
inputRange: [0, 1],
@@ -61,28 +62,37 @@ const Item = (props: ItemProps) => {
}),
).current
const [isDetail, setIsDetail] = useState(false)

useEffect(() => {
if (!selected && isDetail) {
setIsDetail(!isDetail)
Animated.timing(flipAnimation, {
const flipBack = useCallback(() => {
Animated.timing(flipAnimation, {
toValue: 0, // Toggle between 0 (front) and 1 (flipped)
duration: 300,
useNativeDriver: true,
}).start(() => {
Animated.timing(animatedY, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}).start(() => {})
Animated.timing(flipAnimationForScale, {
toValue: 0, // Toggle between 0 (front) and 1 (flipped)
duration: 300,
useNativeDriver: true,
}).start(() => {
Animated.timing(animatedY, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}).start(() => {})
Animated.timing(flipAnimationForScale, {
toValue: 0, // Toggle between 0 (front) and 1 (flipped)
duration: 300,
useNativeDriver: true,
}).start(() => {})
})
}).start(() => {})
})
}, [animatedY, flipAnimation, flipAnimationForScale])
useEffect(() => {
if (toggleDetail) {
setIsDetail(false)
flipBack()
togleDetail(false)
}
}, [flipBack, toggleDetail, togleDetail])
useEffect(() => {
if (!selected && isDetail) {
setIsDetail(!isDetail)
flipBack()
}
}, [animatedY, flipAnimation, flipAnimationForScale, isDetail, selected])
}, [animatedY, flipAnimation, flipAnimationForScale, flipBack, isDetail, selected])

const style = {
transform: [
@@ -96,7 +106,7 @@ const Item = (props: ItemProps) => {
{
scale: scroll.interpolate({
inputRange: [position - 2, position - 1, position, position + 1, position + 2],
outputRange: [scaleFurther, scaleDown, 0.85, scaleDown, scaleFurther],
outputRange: [scaleFurther, scaleDown, 0.9, scaleDown, scaleFurther],
}),
},
{
@@ -135,6 +145,7 @@ const Item = (props: ItemProps) => {
return (
<TouchableWithoutFeedback
key={position}
style={{ backgroundColor: 'red' }}
onPress={() => {
if (selected) {
Animated.timing(flipAnimation, {
@@ -159,6 +170,7 @@ const Item = (props: ItemProps) => {
})
onSelect(position, !isDetail)
} else {
setIsDetail(false)
onSelect(position, false)
}
}}
22 changes: 11 additions & 11 deletions src/components/CoverFlow/convertSensitivity.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { SENSITIVITY_LOW, SENSITIVITY_NORMAL, SENSITIVITY_HIGH } from './constants';
import { SENSITIVITY_HIGH, SENSITIVITY_LOW, SENSITIVITY_NORMAL } from './constants'

export default function convertSensitivity(sensitivity) {
switch (sensitivity) {
case SENSITIVITY_LOW:
return 120;
case SENSITIVITY_HIGH:
return 40;
case SENSITIVITY_NORMAL:
default:
return 60;
}
}
switch (sensitivity) {
case SENSITIVITY_LOW:
return 120
case SENSITIVITY_HIGH:
return 40
case SENSITIVITY_NORMAL:
default:
return 60
}
}
90 changes: 57 additions & 33 deletions src/components/CoverFlow/index.tsx
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import {
SENSITIVITY_NORMAL,
} from './constants'

import { useCoverflowStore } from '@/store/coverflow'
import Item from './Item'
import clamp from './clamp'
import convertSensitivity from './convertSensitivity'
@@ -36,9 +37,12 @@ type CoverflowProps = {
children: any
onPress?: any
onChange?: any
isDetail?: boolean
style?: any
setDetail: (isDetail: boolean) => void
}
const Coverflow = ({
isDetail,
scrollX,
sensitivity = SENSITIVITY_NORMAL,
deceleration = DECELERATION_NORMAL,
@@ -54,24 +58,29 @@ const Coverflow = ({
onPress,
onChange,
style,
setDetail,
...props
}: CoverflowProps) => {
const [selection, setSelection] = useState(initialSelection)
const [layoutWidth, setLayoutWidth] = useState(0)
const scrollStart = useRef(0)
const [childElements, setChildElements] = useState(
fixChildrenOrder({ children }, initialSelection),
)
const { togleDetail } = useCoverflowStore()
const sensitivityValue = useMemo(() => convertSensitivity(sensitivity), [sensitivity])
const scrollPos = useRef(initialSelection)

const onScroll = useCallback(
({ value }: { value: number }) => {
scrollPos.current = value

const newSelection = clamp(Math.round(value), 0, React.Children.count(children) - 1)
if (newSelection !== selection) {
setSelection(newSelection)
setChildElements(fixChildrenOrder({ children }, newSelection))
if (Math.abs(scrollPos.current - value) < 10 || scrollStart.current === 0) {
scrollPos.current = value

const newSelection = clamp(Math.round(value), 0, React.Children.count(children) - 1)
if (newSelection !== selection) {
setSelection(newSelection)
setChildElements(fixChildrenOrder({ children }, newSelection))
}
}
},
[selection, children],
@@ -82,30 +91,54 @@ const Coverflow = ({
return () => {
scrollX.removeListener(listenerId)
}
}, [scrollX, onScroll])
}, [onScroll, scrollX])

const snapToPosition = useCallback(
(pos = scrollPos.current) => {
const count = React.Children.count(children)
const finalPos = clamp(Math.round(pos), 0, count - 1)

if (finalPos !== scrollPos.current) {
setSelection(finalPos)
onChange(finalPos)

Animated.spring(scrollX, {
toValue: finalPos,
bounciness: 1,
useNativeDriver: true,
}).start()
}
},
[children, onChange, scrollX],
)
const panResponder = useMemo(
() =>
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: (_, gestureState) => Math.abs(gestureState.dx) > 10,
onMoveShouldSetPanResponder: (_, gestureState) =>
Math.abs(gestureState.dx) > 10 && !isDetail,
onPanResponderGrant: () => {
scrollX.stopAnimation()
scrollX.extractOffset()
scrollStart.current = 1
},
onPanResponderMove: (_, gestureState) => {
scrollX.setValue(-(gestureState.dx / sensitivityValue))
if ([0].includes(Math.round(scrollPos.current)) && gestureState.dx > 0) {
scrollX.setValue(-(gestureState.dx / 1000))
} else {
// scrollX.setValue(scrollPos.current - gestureState.dx / sensitivityValue)
scrollX.setValue(-(gestureState.dx / sensitivityValue))
}
},
onPanResponderRelease: (_, gestureState) => {
scrollStart.current = 0
scrollX.flattenOffset()

const count = React.Children.count(children)
const newSelection = Math.round(scrollPos.current)

if (newSelection > 0 && newSelection < count - 2 && Math.abs(gestureState.vx) > 1) {
const velocity =
-Math.sign(gestureState.vx) *
(clamp(Math.abs(gestureState.vx), 3, 5) / sensitivityValue)
-Math.sign(gestureState.vx) * (clamp(Math.abs(gestureState.vx), 3, 5) / 60)

Animated.decay(scrollX, {
velocity,
@@ -117,32 +150,23 @@ const Coverflow = ({
}
})
} else {
togleDetail(true)
setDetail(false)
snapToPosition()
}
},
}),
[scrollX, sensitivityValue, children, deceleration],
[
isDetail,
scrollX,
sensitivityValue,
children,
deceleration,
snapToPosition,
togleDetail,
setDetail,
],
)

const snapToPosition = useCallback(
(pos = scrollPos.current) => {
const count = React.Children.count(children)
const finalPos = clamp(Math.round(pos), 0, count - 1)

if (finalPos !== scrollPos.current) {
setSelection(finalPos)
onChange(finalPos)

Animated.spring(scrollX, {
toValue: finalPos,
bounciness: 1,
useNativeDriver: true,
}).start()
}
},
[children, onChange, scrollX],
)

const onLayout = ({ nativeEvent }: { nativeEvent: { layout: { width: number } } }) => {
setLayoutWidth(nativeEvent.layout.width)
}
Loading
Oops, something went wrong.

0 comments on commit 6d57645

Please sign in to comment.