You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现滚动时隐藏顶部与底部导航栏?附Expo Snack示例

实现滚动时隐藏/显示导航栏的交互效果

以下是基于React Native(Expo环境)的具体实现方案,完全贴合YouTube类应用的交互逻辑:

核心逻辑

通过监听滚动组件的滚动事件,结合动画API控制导航栏的位移:

  • 用户向下滚动信息流时,平滑隐藏顶部+底部导航栏,让内容铺满屏幕
  • 滚动停止后,延迟短暂时间再平滑显示导航栏

完整代码实现

import React, { useRef, useEffect } from 'react';
import { View, ScrollView, Animated, StyleSheet, Text } from 'react-native';

const App = () => {
  // 滚动偏移量动画值
  const scrollY = useRef(new Animated.Value(0)).current;
  // 导航栏高度
  const navBarHeight = 60;
  // 滚动停止定时器引用
  const scrollTimerRef = useRef(null);

  // 绑定滚动事件到动画值
  const handleScroll = Animated.event(
    [{ nativeEvent: { contentOffset: { y: scrollY } } }],
    { useNativeDriver: true }
  );

  useEffect(() => {
    // 监听滚动状态
    const scrollListener = scrollY.addListener(({ value }) => {
      // 清除之前的定时器
      if (scrollTimerRef.current) clearTimeout(scrollTimerRef.current);

      // 滚动时隐藏导航栏(判断滚动方向:内容向上滚动时触发)
      if (value > 10) {
        Animated.timing(scrollY, {
          toValue: navBarHeight,
          duration: 250,
          useNativeDriver: true,
        }).start();
      }

      // 滚动停止300ms后显示导航栏
      scrollTimerRef.current = setTimeout(() => {
        Animated.timing(scrollY, {
          toValue: 0,
          duration: 250,
          useNativeDriver: true,
        }).start();
      }, 300);
    });

    // 清理函数
    return () => {
      scrollY.removeListener(scrollListener);
      if (scrollTimerRef.current) clearTimeout(scrollTimerRef.current);
    };
  }, [scrollY, navBarHeight]);

  // 计算顶部导航栏的位移值
  const headerTranslate = scrollY.interpolate({
    inputRange: [0, navBarHeight],
    outputRange: [0, -navBarHeight],
    extrapolate: 'clamp',
  });

  // 计算底部导航栏的位移值
  const footerTranslate = scrollY.interpolate({
    inputRange: [0, navBarHeight],
    outputRange: [0, navBarHeight],
    extrapolate: 'clamp',
  });

  return (
    <View style={styles.container}>
      {/* 顶部导航栏 */}
      <Animated.View style={[styles.navBar, { transform: [{ translateY: headerTranslate }] }]}>
        <Text style={styles.navText}>顶部导航</Text>
      </Animated.View>

      {/* 信息流滚动区域 */}
      <ScrollView
        style={styles.scrollContent}
        onScroll={handleScroll}
        scrollEventThrottle={16} // 保证每秒60次事件触发,动画更流畅
        contentContainerStyle={styles.contentWrap}
      >
        {/* 模拟信息流内容 */}
        {Array.from({ length: 25 }).map((_, idx) => (
          <View key={idx} style={styles.contentItem}>
            <Text>内容卡片 {idx + 1}</Text>
          </View>
        ))}
      </ScrollView>

      {/* 底部导航栏 */}
      <Animated.View style={[styles.navBar, styles.footer, { transform: [{ translateY: footerTranslate }] }]}>
        <Text style={styles.navText}>底部导航</Text>
      </Animated.View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  navBar: {
    position: 'absolute',
    left: 0,
    right: 0,
    height: 60,
    backgroundColor: '#2196F3',
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 100,
  },
  footer: {
    bottom: 0,
  },
  navText: {
    color: '#fff',
    fontSize: 18,
    fontWeight: '600',
  },
  scrollContent: {
    flex: 1,
    marginTop: 60,
    marginBottom: 60,
  },
  contentWrap: {
    paddingVertical: 12,
  },
  contentItem: {
    height: 120,
    marginHorizontal: 16,
    marginVertical: 8,
    backgroundColor: '#f5f5f5',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default App;

关键细节说明

  1. 动画控制:使用Animated.Value和interpolate将滚动偏移量转换为导航栏的垂直位移,实现平滑的隐藏/显示效果
  2. 滚动监听:scrollEventThrottle设置为16,确保滚动事件触发频率足够,动画不会卡顿
  3. 滚动停止判断:通过定时器延迟300ms执行显示动画,避免用户短暂停顿就触发导航栏显示
  4. 边界处理:extrapolate: 'clamp'确保动画值不会超出设定范围,避免导航栏位移异常
  5. 布局适配:滚动区域设置marginTop和marginBottom,避免内容被固定定位的导航栏遮挡

如果你使用的是FlatList而非ScrollView,只需要替换组件并保持onScroll等逻辑即可,实现效果完全一致。

内容的提问来源于stack exchange,提问作者Alan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 15:02:51