import React, { useRef, useState, useCallback, useMemo } from 'react';
import { View, ScrollView, Text, TouchableOpacity, StyleSheet, Dimensions } from 'react-native';
const { height: screenHeight } = Dimensions.get('window');
const CATEGORIES = [
{ id: 1, title: 'Appetizers', color: '#FF6B6B' },
{ id: 2, title: 'Main Courses', color: '#4ECDC4' },
{ id: 3, title: 'Desserts', color: '#45B7D1' },
{ id: 4, title: 'Beverages', color: '#96CEB4' },
{ id: 5, title: 'Specials', color: '#FECA57' }
];
const ITEMS_PER_CATEGORY = 8;
export default function ScrollViewAnchorExample() {
const scrollViewRef = useRef(null);
const [selectedCategoryId, setSelectedCategoryId] = useState(1);
const [categoryPositions, setCategoryPositions] = useState(new Map());
const [showTitleInHeader, setShowTitleInHeader] = useState(false);
const [headerPosition, setHeaderPosition] = useState(0);
const [isScrolling, setIsScrolling] = useState(false); // Добавляем флаг скролла
const lastScrollY = useRef(0); // Для throttling
// Скролл к якорю категории с проверками
const scrollToCategory = useCallback((categoryId: number) => {
console.log('=== CATEGORY SELECT DEBUG ===');
console.log('Selected category:', categoryId);
console.log('Current selectedCategoryId:', selectedCategoryId);
console.log('Is scrolling:', isScrolling);
console.log('ScrollView ref exists:', !!scrollViewRef.current);
console.log('Category positions size:', categoryPositions.size);
console.log('Timestamp:', Date.now());
// Проверка на повторные вызовы во время скролла
if (isScrolling) {
console.log('
return;
}
const categoryPosition = categoryPositions.get(categoryId);
console.log('Category position for', categoryId, ':', categoryPosition);
if (categoryPosition !== undefined && scrollViewRef.current) {
const offsetY = Math.max(0, categoryPosition - 100);
console.log('Scrolling to offsetY:', offsetY);
// Устанавливаем флаг скролла
setIsScrolling(true);
scrollViewRef.current.scrollTo({
y: offsetY,
animated: true
});
// Обновляем выбранную категорию
setTimeout(() => {
console.log('Setting selected category to:', categoryId);
setSelectedCategoryId(categoryId);
}, 50);
// Сбрасываем флаг скролла через задержку
setTimeout(() => {
console.log('Resetting isScrolling flag');
setIsScrolling(false);
}, 600); // Увеличиваем время для анимации
} else {
console.log('
}
console.log('================================');
}, [categoryPositions, selectedCategoryId, isScrolling]);
// Обработчик layout категории для сохранения позиции якоря
const handleCategoryLayout = useCallback((categoryId: number) => {
return (event: any) => {
const { y } = event.nativeEvent.layout;
console.log(`
setCategoryPositions(prev => new Map(prev.set(categoryId, y)));
};
}, []);
// Обработчик скролла с throttling
const handleScroll = useCallback((event: any) => {
const scrollY = event.nativeEvent.contentOffset.y;
// Throttling для уменьшения частоты обновлений
if (Math.abs(scrollY - lastScrollY.current) < 5) return;
lastScrollY.current = scrollY;
// Показываем заголовок в header при скролле
setShowTitleInHeader(scrollY > headerPosition + 50);
// Автоматическое определение активной категории только если не скроллим вручную
if (!isScrolling) {
let activeCategory = 1;
categoryPositions.forEach((position, categoryId) => {
if (scrollY >= position - 150) {
activeCategory = categoryId;
}
});
if (activeCategory !== selectedCategoryId) {
console.log('
setSelectedCategoryId(activeCategory);
}
}
}, [headerPosition, categoryPositions, selectedCategoryId, isScrolling]);
const handleHeaderLayout = useCallback((event: any) => {
const { y } = event.nativeEvent.layout;
setHeaderPosition(y);
console.log('
}, []);
// Обработчик завершения скролла
const handleMomentumScrollEnd = useCallback(() => {
console.log('
setIsScrolling(false);
}, []);
// Обработчик начала скролла пользователем
const handleScrollBeginDrag = useCallback(() => {
console.log('
setIsScrolling(true);
}, []);
// Мемоизированные элементы категорий для производительности
const categoryItems = useMemo(() => CATEGORIES, []);
return (
{/* Основной заголовок */}
{showTitleInHeader ? 'Menu Categories' : 'Restaurant Menu'}
{/* Добавляем индикатор состояния для отладки */}
Category: {selectedCategoryId} | Scrolling: {isScrolling ? 'YES' : 'NO'}
{/* Заголовочная секция */}
Welcome to Our Restaurant
Delicious food awaits you
{/* Sticky Header с категориями */}
{categoryItems.map((category) => (
{
console.log('
scrollToCategory(category.id);
}}
// Дополнительные свойства для надежности
activeOpacity={0.7}
delayPressIn={0}
disabled={isScrolling} // Блокируем нажатия во время скролла
>
{category.title}
))}
{/* Контент с категориями */}
{categoryItems.map((category) => (
{category.title}
{/* Элементы в категории */}
{Array.from({ length: ITEMS_PER_CATEGORY }, (_, index) => (
{category.title} Item {index + 1}
Description of the delicious {category.title.toLowerCase()} item
${(Math.random() * 20 + 5).toFixed(2)}
))}
))}
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
header: {
backgroundColor: '#000',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#333',
},
headerTitle: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
// Добавляем стиль для отладочного текста
debugText: {
color: '#666',
fontSize: 12,
marginTop: 4,
},
heroSection: {
backgroundColor: '#1a1a1a',
padding: 32,
alignItems: 'center',
minHeight: 200,
justifyContent: 'center',
},
heroTitle: {
color: 'white',
fontSize: 28,
fontWeight: 'bold',
marginBottom: 8,
textAlign: 'center',
},
heroSubtitle: {
color: '#ccc',
fontSize: 16,
textAlign: 'center',
},
stickyHeader: {
backgroundColor: '#000',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#333',
},
categoriesContainer: {
paddingHorizontal: 16,
},
categoryChip: {
backgroundColor: '#333',
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
marginRight: 12,
},
categoryChipActive: {
backgroundColor: '#007AFF',
},
categoryText: {
color: '#ccc',
fontWeight: '500',
fontSize: 14,
},
categoryTextActive: {
color: 'white',
fontWeight: 'bold',
},
contentContainer: {
backgroundColor: '#000',
minHeight: screenHeight,
},
categorySection: {
paddingHorizontal: 16,
paddingTop: 24,
},
categoryTitle: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
paddingBottom: 8,
borderBottomWidth: 2,
borderBottomColor: 'currentColor',
},
menuItem: {
backgroundColor: '#1a1a1a',
padding: 16,
marginBottom: 12,
borderRadius: 8,
borderLeftWidth: 4,
},
itemTitle: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
marginBottom: 4,
},
itemDescription: {
color: '#ccc',
fontSize: 14,
marginBottom: 8,
lineHeight: 20,
},
itemPrice: {
color: '#4CAF50',
fontSize: 16,
fontWeight: 'bold',
},
bottomSpacing: {
height: 100,
},
});
Подробнее здесь: https://stackoverflow.com/questions/797 ... derindices
Мобильная версия