1) 화면 전체 사이즈를 가지는 여러개의 컴포넌트(섹션)가 있고 그에 따른 스크롤 위치에 따른 상단 메뉴탭 체크 상태 변경
2) 목록이 여러 개 있고 그 위치에 따른 상단 메뉴탭 체크 상태 변경
아이디어
예시
코드
import React, { useRef, useState } from "react";
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Dimensions,
} from "react-native";
export default function App() {
// ScrollView를 제어하기 위한 ref
const scrollViewRef = useRef();
// 현재 하이라이트된 버튼(현재 어느 섹션을 보고 있는 지)을 추적하는 상태
const [highlightedButton, setHighlightedButton] = useState(1);
// 특정 섹션으로 스크롤하는 함수
const scrollToSection = (sectionNumber) => {
const yOffset = sectionNumber * Dimensions.get("window").height;
scrollViewRef.current.scrollTo({ y: yOffset, animated: true });
};
// ScrollView의 스크롤 이벤트를 처리하여 현재 섹션 추적
const handleScroll = (event) => {
const offsetY = event.nativeEvent.contentOffset.y;
// 스크롤 위치를 기반으로 현재 섹션 번호 계산
const sectionNumber =
Math.round(offsetY / Dimensions.get("window").height) + 1;
// 하이라이트된 버튼 상태 업데이트
setHighlightedButton(sectionNumber);
};
return (
<View style={styles.container}>
{/* 메뉴 영역 */}
<View style={styles.menu}>
{[0, 1, 2].map((number) => (
<TouchableOpacity
key={number}
onPress={() => scrollToSection(number)}
style={[
styles.menuItem,
// 현재 섹션과 일치하는 경우 버튼 스타일 변경
highlightedButton === number + 1 && styles.highlightedButton,
]}
>
<Text>{number + 1}</Text>
</TouchableOpacity>
))}
</View>
{/* 섹션들을 담고 있는 ScrollView */}
<ScrollView
ref={scrollViewRef}
onScroll={handleScroll}
scrollEventThrottle={16}
>
{/* 각 섹션 */}
<View style={[styles.fullHeightView, { backgroundColor: "lightblue" }]}>
<Text>Section 1</Text>
</View>
<View
style={[styles.fullHeightView, { backgroundColor: "lightgreen" }]}
>
<Text>Section 2</Text>
</View>
<View
style={[styles.fullHeightView, { backgroundColor: "lightcoral" }]}
>
<Text>Section 3</Text>
</View>
</ScrollView>
</View>
);
}
// 스타일 정의
const styles = StyleSheet.create({
container: {
paddingTop: 50,
flex: 1,
flexDirection: "column",
},
menu: {
flexDirection: "row",
justifyContent: "space-around",
backgroundColor: "lightgray",
},
menuItem: {
padding: 10,
fontSize: 20,
},
highlightedButton: {
backgroundColor: "yellow", // 하이라이트된 버튼의 배경색 변경
},
fullHeightView: {
minHeight: Dimensions.get("window").height,
alignItems: "center",
justifyContent: "center",
},
});
아이디어
예시
코드
// [App.js]
import React, { useRef, useState } from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import MultiVerticalList from "./MultiVerticalList";
const YourScreen = () => {
// ScrollView를 조작하기 위한 ref
const scrollViewRef = useRef();
// 현재 하이라이트된 버튼(현재 어느 목록을 보고 있는 지)을 추적하는 상태
const [highlightedButton, setHighlightedButton] = useState(1);
// 더미 목록 데이터
const data = [
{
title: "List 1",
items: Array.from({ length: 50 }, (_, i) => ({
id: i,
title: `Item ${i}`,
})),
},
];
// 목록으로 스크롤하는 함수
const scrollToSection = (sectionIndex) => {
setHighlightedButton(sectionIndex);
if (scrollViewRef.current) {
scrollViewRef.current.scrollToIndex({
animated: true,
index: sectionIndex,
});
}
};
// 상단 메뉴에 표시될 목록 인덱스 배열
const topMenu = [0, 15, 32];
return (
<View style={styles.container}>
{/* 상단 메뉴 */}
<View style={styles.menu}>
{topMenu.map((number, i) => (
<TouchableOpacity
key={number}
onPress={() => scrollToSection(number)}
style={[
styles.menuItem,
// 강조된 버튼 스타일링
i < topMenu.length - 1
? highlightedButton >= number &&
styles.highlightedButton &&
highlightedButton < topMenu[i + 1] &&
styles.highlightedButton
: highlightedButton >= number && styles.highlightedButton,
]}
>
<Text>{number}</Text>
</TouchableOpacity>
))}
</View>
{/* 세로로 여러 리스트를 표시하는 컴포넌트 */}
<MultiVerticalList
ref={scrollViewRef}
data={data}
// 스크롤 시 강조된 버튼 업데이트
onScroll={(index) => setHighlightedButton(index)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 50,
marginBottom: 50,
flex: 1,
flexDirection: "column",
},
menu: {
flexDirection: "row",
justifyContent: "space-around",
backgroundColor: "lightgray",
},
menuItem: {
padding: 10,
fontSize: 20,
},
highlightedButton: {
backgroundColor: "yellow",
},
});
export default YourScreen;
// [MultiVerticalList.jsx]
import React, { forwardRef, useRef } from "react";
import { View, FlatList, Text } from "react-native";
const MultiVerticalList = forwardRef(({ data, onScroll }, ref) => {
const flatListRef = useRef();
// 스크롤 이벤트 핸들러
const handleScroll = (event) => {
const offsetY = event.nativeEvent.contentOffset.y;
// 현재 스크롤 위치에 해당하는 섹션의 인덱스 계산
const index = findSectionIndex(offsetY);
if (onScroll) {
onScroll(index);
}
};
// 주어진 스크롤 위치에 해당하는 섹션 인덱스 찾기
const findSectionIndex = (offsetY) => {
let index = 0;
for (let i = 0; i < data[0].items.length; i++) {
if (offsetY < i * 40) {
break;
}
index = i;
}
return index;
};
// 각 리스트 아이템 렌더링 함수
const renderList = ({ item }) => (
<View
style={{ marginVertical: 10 }}
onLayout={(event) => {
// 리스트 아이템의 레이아웃 정보를 얻어오기
const layout = event.nativeEvent.layout;
const position = layout.y;
}}
>
<Text>{item.title}</Text>
</View>
);
// 리스트 아이템 간 구분선 렌더링 함수
const renderSeparator = () => (
<View style={{ height: 1, backgroundColor: "#ccc" }} />
);
return (
// FlatList 컴포넌트로 섹션과 아이템 표시
<FlatList
ref={(listRef) => {
flatListRef.current = listRef;
// 부모 컴포넌트에서 ref 사용 가능하도록 설정
if (ref) {
ref.current = listRef;
}
}}
data={data.flatMap((listData) => listData.items)}
renderItem={renderList}
keyExtractor={(item) => item.id.toString()}
ItemSeparatorComponent={renderSeparator}
// 스크롤 이벤트 처리
onScroll={handleScroll}
scrollEventThrottle={16}
/>
);
});
export default MultiVerticalList;