动画滚动视图中的 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 。并在滚动处理程序中检查变量是否为真,然后不要在其中执行