useAnimatedScrollHandler

useAnimatedScrollHandler() is Reanimated 11,010 's version of Animated.event(), with one difference that matters: its handlers are worklets, so you can run any logic per scroll event on the UI thread, not just copy a number. It accepts onScroll, onBeginDrag, onEndDrag, onMomentumBegin and onMomentumEnd, and you pass the result to a Reanimated scroll component. This header shrinks by animating height, a layout prop that Animated's native driver refuses:

src/rea/ScrollHeader.tsx: a header that shrinks and darkens as the list scrolls (excerpt)TSX
  const top = useSafeAreaInsets().top;
  const y = useSharedValue(0);
  const onScroll = useAnimatedScrollHandler({
    onScroll: (e) => { y.value = e.contentOffset.y; },          // UI thread, every event
    onEndDrag: (e) => { scheduleOnRN(say, 'end drag at', Math.round(e.contentOffset.y)); },
    onMomentumEnd: (e) => {
      scheduleOnRN(say, 'momentum end at', Math.round(e.contentOffset.y));
    },
  });
  const header = useAnimatedStyle(() => ({
    height: top + interpolate(y.value, [0, 100], [120, 56], Extrapolation.CLAMP), // layout
    backgroundColor: y.value > 100 ? '#0b1320' : '#1565c0',
  }));
// ...header styles an Animated.View above <Animated.FlatList onScroll={onScroll} ...>

Two drags with adb shell input swipe:

Output of 103
 LOG  end drag at 118
 LOG  momentum end at 118
 LOG  end drag at 318
 LOG  momentum end at 318

interpolate() and interpolateColor() are worklet versions of Interpolation's ideas. Animating height makes Yoga lay out the header and the list every frame: fine for one header, but prefer transforms for many views. useScrollOffset(animatedRef) returns the offset as a shared value without a handler.