动画滚动视图中的 scrollHandler 和 scrollTo
scrollHandler and scrollTo in animated scrollview
有没有办法同时使用 scrollHandler(用于更新共享值)和 scrollTo
https://snack.expo.dev/@haniq313/scrollview-with-handler-and-scrollto
点心和代码如下。
问题是当我按下“下一步”按钮时,scrollTo 起作用但也触发了 scrollHandler。如果我禁用 scrollHandler 那么它工作正常。但后来我无法手动滑动和更改值。需要使两者都起作用。有没有办法调用 scrollTo 并且同时不触发 scrollHandler?
import React, { useState } from 'react';
import {
Text,
View,
StyleSheet,
ScrollView,
Platform,
SafeAreaView,
TouchableOpacity,
} from 'react-native';
import Constants from 'expo-constants';
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
scrollTo,
} from 'react-native-reanimated';
import { Ionicons } from '@expo/vector-icons';
const data = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
// You can import from local files
import AssetExample from './components/AssetExample';
const ITEM_HEIGHT = 25;
const ITEM_WIDTH = 200;
const VISIBLE_ITEMS = 3;
const FONT_SIZE = 16;
const SpinnerItem = (props) => {
const { idx, label, scrollY } = props;
const focusedStyle = useAnimatedStyle(() => {
const opacity = interpolate(
scrollY.value,
[(idx - 2) * ITEM_HEIGHT, (idx - 1) * ITEM_HEIGHT, idx * ITEM_HEIGHT],
[0.3, 1, 0.3],
Extrapolate.CLAMP
);
const rotate = interpolate(
scrollY.value,
[(idx - 2) * ITEM_HEIGHT, (idx - 1) * ITEM_HEIGHT, idx * ITEM_HEIGHT],
[-60, 0, 60],
Extrapolate.CLAMP
);
return {
opacity,
transform: [{ rotateX: rotate + 'deg' }],
};
}, [idx, scrollY]);
const fontStyle = useAnimatedStyle(() => {
const fontStyle = interpolate(
scrollY.value,
[(idx - 2) * ITEM_HEIGHT, (idx - 1) * ITEM_HEIGHT, idx * ITEM_HEIGHT],
[FONT_SIZE, FONT_SIZE + 8, FONT_SIZE],
Extrapolate.CLAMP
);
return {
fontSize: fontStyle,
};
}, [idx, scrollY]);
return (
<Animated.View
style={[
{
alignItems: 'center',
justifyContent: 'center',
width: ITEM_WIDTH,
height: ITEM_HEIGHT,
},
focusedStyle,
]}>
<Animated.Text
allowFontScaling={false}
adjustsFontSizeToFit={false}
style={[styles.label, fontStyle]}>
{label}
</Animated.Text>
</Animated.View>
);
};
export default function App() {
const [selectedIndex, setSelectedIndex] = useState(0);
const scrollY = useSharedValue(0);
const aref = useAnimatedRef();
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollY.value = event.contentOffset.y;
runOnJS(setSelectedIndex)(Math.round(scrollY.value / ITEM_HEIGHT));
},
});
useDerivedValue(() => {
scrollTo(aref, 0, scrollY.value, true);
});
const incrementScroll = () => {
'worklet';
scrollY.value = scrollY.value + ITEM_HEIGHT;
if (scrollY.value >= (data.length - 1) * ITEM_HEIGHT) scrollY.value = 0;
runOnJS(setSelectedIndex)(Math.round(scrollY.value / ITEM_HEIGHT));
};
return (
<SafeAreaView
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>{'Selected Index: ' + selectedIndex}</Text>
<View style={styles.container}>
<TouchableOpacity
onPress={() => {
incrementScroll();
}}>
<Ionicons name="ios-arrow-down-sharp" size={24} color="black" />
</TouchableOpacity>
<Animated.ScrollView
showsVerticalScrollIndicator={false}
bounces={false}
ref={aref}
decelerationRate={Platform.OS === 'ios' ? 0 : 0.98}
renderToHardwareTextureAndroid
contentContainerStyle={{
alignItems: 'center',
justifyContent: 'center',
}}
centerContent
pinchGestureEnabled={false}
snapToInterval={ITEM_HEIGHT}
snapToAlignment="start"
onScroll={scrollHandler}
scrollEventThrottle={16}>
{['spacer', ...data, 'spacer'].map((item, idx) => (
<SpinnerItem
key={idx}
idx={idx}
scrollY={scrollY}
label={item === 'spacer' ? '' : item}
/>
))}
</Animated.ScrollView>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
backgroundColor: '#ecf0f1',
height: ITEM_HEIGHT * 3,
width: ITEM_WIDTH,
overflow: 'hidden',
},
label: {
textAlignVertical: 'center',
textAlign: 'center',
color: 'red',
fontSize: FONT_SIZE,
fontWeight: '700',
},
});
试试这个:link
您可以简单地添加另一个动画变量并在滚动前将其设置为 true 并在滚动后将其设置为 false 。并在滚动处理程序中检查变量是否为真,然后不要在其中执行
有没有办法同时使用 scrollHandler(用于更新共享值)和 scrollTo
https://snack.expo.dev/@haniq313/scrollview-with-handler-and-scrollto
点心和代码如下。
问题是当我按下“下一步”按钮时,scrollTo 起作用但也触发了 scrollHandler。如果我禁用 scrollHandler 那么它工作正常。但后来我无法手动滑动和更改值。需要使两者都起作用。有没有办法调用 scrollTo 并且同时不触发 scrollHandler?
import React, { useState } from 'react';
import {
Text,
View,
StyleSheet,
ScrollView,
Platform,
SafeAreaView,
TouchableOpacity,
} from 'react-native';
import Constants from 'expo-constants';
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
scrollTo,
} from 'react-native-reanimated';
import { Ionicons } from '@expo/vector-icons';
const data = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
// You can import from local files
import AssetExample from './components/AssetExample';
const ITEM_HEIGHT = 25;
const ITEM_WIDTH = 200;
const VISIBLE_ITEMS = 3;
const FONT_SIZE = 16;
const SpinnerItem = (props) => {
const { idx, label, scrollY } = props;
const focusedStyle = useAnimatedStyle(() => {
const opacity = interpolate(
scrollY.value,
[(idx - 2) * ITEM_HEIGHT, (idx - 1) * ITEM_HEIGHT, idx * ITEM_HEIGHT],
[0.3, 1, 0.3],
Extrapolate.CLAMP
);
const rotate = interpolate(
scrollY.value,
[(idx - 2) * ITEM_HEIGHT, (idx - 1) * ITEM_HEIGHT, idx * ITEM_HEIGHT],
[-60, 0, 60],
Extrapolate.CLAMP
);
return {
opacity,
transform: [{ rotateX: rotate + 'deg' }],
};
}, [idx, scrollY]);
const fontStyle = useAnimatedStyle(() => {
const fontStyle = interpolate(
scrollY.value,
[(idx - 2) * ITEM_HEIGHT, (idx - 1) * ITEM_HEIGHT, idx * ITEM_HEIGHT],
[FONT_SIZE, FONT_SIZE + 8, FONT_SIZE],
Extrapolate.CLAMP
);
return {
fontSize: fontStyle,
};
}, [idx, scrollY]);
return (
<Animated.View
style={[
{
alignItems: 'center',
justifyContent: 'center',
width: ITEM_WIDTH,
height: ITEM_HEIGHT,
},
focusedStyle,
]}>
<Animated.Text
allowFontScaling={false}
adjustsFontSizeToFit={false}
style={[styles.label, fontStyle]}>
{label}
</Animated.Text>
</Animated.View>
);
};
export default function App() {
const [selectedIndex, setSelectedIndex] = useState(0);
const scrollY = useSharedValue(0);
const aref = useAnimatedRef();
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollY.value = event.contentOffset.y;
runOnJS(setSelectedIndex)(Math.round(scrollY.value / ITEM_HEIGHT));
},
});
useDerivedValue(() => {
scrollTo(aref, 0, scrollY.value, true);
});
const incrementScroll = () => {
'worklet';
scrollY.value = scrollY.value + ITEM_HEIGHT;
if (scrollY.value >= (data.length - 1) * ITEM_HEIGHT) scrollY.value = 0;
runOnJS(setSelectedIndex)(Math.round(scrollY.value / ITEM_HEIGHT));
};
return (
<SafeAreaView
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>{'Selected Index: ' + selectedIndex}</Text>
<View style={styles.container}>
<TouchableOpacity
onPress={() => {
incrementScroll();
}}>
<Ionicons name="ios-arrow-down-sharp" size={24} color="black" />
</TouchableOpacity>
<Animated.ScrollView
showsVerticalScrollIndicator={false}
bounces={false}
ref={aref}
decelerationRate={Platform.OS === 'ios' ? 0 : 0.98}
renderToHardwareTextureAndroid
contentContainerStyle={{
alignItems: 'center',
justifyContent: 'center',
}}
centerContent
pinchGestureEnabled={false}
snapToInterval={ITEM_HEIGHT}
snapToAlignment="start"
onScroll={scrollHandler}
scrollEventThrottle={16}>
{['spacer', ...data, 'spacer'].map((item, idx) => (
<SpinnerItem
key={idx}
idx={idx}
scrollY={scrollY}
label={item === 'spacer' ? '' : item}
/>
))}
</Animated.ScrollView>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
backgroundColor: '#ecf0f1',
height: ITEM_HEIGHT * 3,
width: ITEM_WIDTH,
overflow: 'hidden',
},
label: {
textAlignVertical: 'center',
textAlign: 'center',
color: 'red',
fontSize: FONT_SIZE,
fontWeight: '700',
},
});
试试这个:link
您可以简单地添加另一个动画变量并在滚动前将其设置为 true 并在滚动后将其设置为 false 。并在滚动处理程序中检查变量是否为真,然后不要在其中执行