Files
jrshikoku/components/StationDiagram/ExGridView.tsx
harukin-expo-dev-env 9410925f70 駅名カラー設定
2025-08-27 00:19:39 +00:00

201 lines
5.9 KiB
TypeScript

import { FC, useRef, useState, useCallback } from "react";
import { View, Text, ScrollView, useWindowDimensions } from "react-native";
import { ExGridViewItem } from "./ExGridViewItem";
import Animated, {
useAnimatedStyle,
useSharedValue,
runOnJS,
useAnimatedScrollHandler,
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
export const ExGridView: FC<{
data: {
trainNumber: string;
array: string;
name: string;
type: string;
time: string;
}[];
}> = ({ data }) => {
const groupedData = {};
const groupKeys = [];
const { width } = useWindowDimensions();
data.forEach((item) => {
const hour = item.time.split(":")[0];
if (!groupedData[hour]) {
groupedData[hour] = [];
groupKeys.push(hour);
}
groupedData[hour].push(item);
});
// ドラッグ位置を保持する共有値
const widthX = useSharedValue(width);
const savedWidthX = useSharedValue(width);
const [scrollEnabled, setScrollEnabled] = useState(true);
const scrollRef = useRef<Animated.ScrollView>(null);
// ScrollViewの有効/無効を切り替える関数
const toggleScrollEnabled = useCallback((enabled: boolean) => {
setScrollEnabled(enabled);
}, []);
// パンジェスチャー(ドラッグ)のハンドラー
const pinchGesture = Gesture.Pinch()
.onUpdate((e) => {
const calc = savedWidthX.value * e.scale;
widthX.value = calc > width ? calc : width;
//runOnJS(scrollToRightEnd)();
})
.onEnd(() => {
savedWidthX.value = widthX.value;
});
const gesture = Gesture.Pan()
.minPointers(2) // 最低2本指
.maxPointers(2) // 最大2本指
.onTouchesDown((e) => {
if (e.numberOfTouches >= 2) runOnJS(toggleScrollEnabled)(false);
})
.onTouchesUp((e) => {
runOnJS(toggleScrollEnabled)(true);
})
.onEnd((e) => {
runOnJS(toggleScrollEnabled)(true);
});
// ジェスチャーを組み合わせる
const composed = Gesture.Simultaneous(pinchGesture, gesture);
// アニメーションスタイル
const animatedStyle = useAnimatedStyle(() => ({
width: widthX.value,
}));
// 時ヘッダーを横にスクロールしたときの処理
const scrollX = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollX.value = event.contentOffset.x;
},
});
const stickyTextStyle = useAnimatedStyle(() => ({
transform: [{ translateX: scrollX.value }],
}));
return (
<GestureDetector gesture={composed}>
<Animated.ScrollView
horizontal
nestedScrollEnabled
pinchGestureEnabled={false}
scrollEnabled={scrollEnabled}
onScroll={scrollHandler}
onContentSizeChange={(contentWidth) => {
// 現在のスクロール位置を取得
const currentScrollX = scrollX.value;
const containerWidth = width - 50;
// コンテンツが画面からはみ出している場合のみ右端にスクロール
if (currentScrollX + containerWidth > contentWidth) {
const newScrollX = Math.max(0, contentWidth - containerWidth);
scrollRef.current?.scrollTo({ x: newScrollX, animated: true });
}
}}
ref={scrollRef}
contentContainerStyle={{
flexDirection: "column",
backgroundColor: "white",
}}
>
<Animated.View
style={[
{
width: width,
flexDirection: "row",
},
animatedStyle,
]}
>
{Array.from({ length: 60 }, (_, i) => i + 1).map((num) => {
if (num % 5 === 0) {
return (
<Text
key={num}
style={{
flex: 1,
textAlign: "center",
borderRightWidth: 0.5,
borderColor: "#ccc",
flexWrap: "nowrap",
fontSize: 12,
}}
>
{num - 5}
</Text>
);
} else return <></>;
})}
<Text
style={{
textAlign: "center",
borderRightWidth: 0.5,
borderColor: "#ccc",
flexWrap: "nowrap",
fontSize: 12,
width: 50,
}}
>
()
</Text>
</Animated.View>
<Animated.ScrollView
style={[{ width: width }, animatedStyle]}
pinchGestureEnabled={false}
minimumZoomScale={0.5}
maximumZoomScale={3.0}
scrollEnabled={scrollEnabled}
stickyHeaderIndices={
groupKeys.at(0) ? groupKeys.map((_, i) => i * 2) : []
}
>
{groupKeys.map((hour) => [
<View
style={{
padding: 5,
borderBottomWidth: 0.5,
borderTopWidth: 0.5,
borderBottomColor: "#ccc",
backgroundColor: "#f0f0f0",
}}
key={hour}
>
<Animated.Text
style={[
{
fontSize: 15,
zIndex: 1,
marginLeft: 0,
},
stickyTextStyle,
]}
>
{hour}
</Animated.Text>
</View>,
<View style={{ flexDirection: "row", position: "relative" }}>
{groupedData[hour].map((d, i,array) => (
<ExGridViewItem
key={d.trainNumber + i}
d={d}
index={i}
width={widthX}
array={array}
/>
))}
</View>,
])}
</Animated.ScrollView>
</Animated.ScrollView>
</GestureDetector>
);
};