横スクロールのサイズ変更をピンチでできるようにした

This commit is contained in:
harukin-expo-dev-env
2025-08-26 14:38:11 +00:00
parent 5f7c4d202d
commit edc1dc5b2d
2 changed files with 114 additions and 74 deletions

View File

@@ -1,7 +1,11 @@
import { FC } from "react"; import { FC } from "react";
import { ListViewItem } from "@/components/StationDiagram/ListViewItem";
import { View, Text, ScrollView, useWindowDimensions } from "react-native"; import { View, Text, ScrollView, useWindowDimensions } from "react-native";
import { ExGridViewItem } from "./ExGridViewItem"; import { ExGridViewItem } from "./ExGridViewItem";
import Animated, {
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
export const ExGridView: FC<{ export const ExGridView: FC<{
data: { data: {
@@ -15,7 +19,7 @@ export const ExGridView: FC<{
const groupedData = {}; const groupedData = {};
const groupKeys = []; const groupKeys = [];
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
data.forEach((item) => { data.forEach((item) => {
const hour = item.time.split(":")[0]; const hour = item.time.split(":")[0];
if (!groupedData[hour]) { if (!groupedData[hour]) {
@@ -24,37 +28,61 @@ export const ExGridView: FC<{
} }
groupedData[hour].push(item); groupedData[hour].push(item);
}); });
// ドラッグ位置を保持する共有値
const widthX = useSharedValue(width);
const savedWidthX = useSharedValue(width);
// パンジェスチャー(ドラッグ)のハンドラー
const pinchGesture = Gesture.Pinch()
.onUpdate((e) => {
const calc = savedWidthX.value * e.scale;
widthX.value = calc > width ? calc : width;
})
.onEnd(() => {
savedWidthX.value = widthX.value;
});
// アニメーションスタイル
const animatedStyle = useAnimatedStyle(() => ({
width: widthX.value,
}));
return ( return (
<ScrollView horizontal nestedScrollEnabled> <GestureDetector gesture={pinchGesture}>
<ScrollView <ScrollView horizontal nestedScrollEnabled pinchGestureEnabled={false}>
style={{ backgroundColor: "white", width: width }} <Animated.ScrollView
minimumZoomScale={0.5} style={[{ backgroundColor: "white", width: width }, animatedStyle]}
maximumZoomScale={3.0} pinchGestureEnabled={false}
pinchGestureEnabled minimumZoomScale={0.5}
stickyHeaderIndices={ maximumZoomScale={3.0}
groupKeys.at(0) ? groupKeys.map((_, i) => i * 2) : [] stickyHeaderIndices={
} groupKeys.at(0) ? groupKeys.map((_, i) => i * 2) : []
> }
{groupKeys.map((hour) => [ >
<View {groupKeys.map((hour) => [
style={{ <View
backgroundColor: "white", style={{
padding: 5, backgroundColor: "white",
borderBottomWidth: 0.5, padding: 5,
borderTopWidth: 0.5, borderBottomWidth: 0.5,
borderBottomColor: "#ccc", borderTopWidth: 0.5,
}} borderBottomColor: "#ccc",
key={hour} }}
> key={hour}
<Text style={{ fontSize: 15 }}>{hour}</Text> >
</View>, <Text style={{ fontSize: 15 }}>{hour}</Text>
<View style={{ flexDirection: "row", position: "relative" }}> </View>,
{groupedData[hour].map((d, i) => ( <View style={{ flexDirection: "row", position: "relative" }}>
<ExGridViewItem key={d.trainNumber + i} d={d} index={i} /> {groupedData[hour].map((d, i) => (
))} <ExGridViewItem
</View>, key={d.trainNumber + i}
])} d={d}
index={i}
width={widthX}
/>
))}
</View>,
])}
</Animated.ScrollView>
</ScrollView> </ScrollView>
</ScrollView> </GestureDetector>
); );
}; };

View File

@@ -2,7 +2,7 @@ import { migrateTrainName } from "@/lib/eachTrainInfoCoreLib/migrateTrainName";
import { getStringConfig, typeID } from "@/lib/getStringConfig"; import { getStringConfig, typeID } from "@/lib/getStringConfig";
import { getTrainType } from "@/lib/getTrainType"; import { getTrainType } from "@/lib/getTrainType";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram"; import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { FC, useEffect, useMemo, useState } from "react"; import { FC, useEffect, useLayoutEffect, useMemo, useState } from "react";
import { import {
View, View,
Text, Text,
@@ -15,6 +15,8 @@ import { SheetManager } from "react-native-actions-sheet";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { lineList } from "@/lib/getStationList"; import { lineList } from "@/lib/getStationList";
import { useStationList } from "@/stateBox/useStationList"; import { useStationList } from "@/stateBox/useStationList";
import { SharedValue, useAnimatedStyle } from "react-native-reanimated";
import Animated from "react-native-reanimated";
export const ExGridViewItem: FC<{ export const ExGridViewItem: FC<{
d: { d: {
@@ -25,7 +27,8 @@ export const ExGridViewItem: FC<{
time: string; time: string;
}; };
index: number; index: number;
}> = ({ d, index }) => { width: SharedValue<number>;
}> = ({ d, index, width }) => {
const { allCustomTrainData } = useAllTrainDiagram(); const { allCustomTrainData } = useAllTrainDiagram();
const { navigate, goBack } = useNavigation(); const { navigate, goBack } = useNavigation();
const [trainData, setTrainData] = useState<{ const [trainData, setTrainData] = useState<{
@@ -53,7 +56,6 @@ export const ExGridViewItem: FC<{
}); });
} }
}, []); }, []);
const { width } = useWindowDimensions();
const { color, name, data } = getTrainType(trainData?.type, true); const { color, name, data } = getTrainType(trainData?.type, true);
const { originalStationList } = useStationList(); const { originalStationList } = useStationList();
// 列車名、種別、フォントの取得 // 列車名、種別、フォントの取得
@@ -173,48 +175,58 @@ export const ExGridViewItem: FC<{
// if(typeString == "回送"){ // if(typeString == "回送"){
// return<></>; // return<></>;
// } // }
const animatedStyle = useAnimatedStyle(() => {
const leftPosition =
((((width.value - 50) / 100) * parseInt(formattedTime)) / 60) * 100;
return {
left: leftPosition,
};
}, [formattedTime]);
return ( return (
<View style={{ left: 0, height: 50 }}> <View style={{ left: 0, height: 50 }}>
<TouchableOpacity <Animated.View
style={{ style={[
flexDirection: "column", {
borderTopWidth: 1, flexDirection: "column",
borderBottomWidth: 0.5, borderTopWidth: 1,
borderStyle: "solid", borderBottomWidth: 0.5,
borderColor: "darkgray", borderStyle: "solid",
opacity: d.type.includes("通") ? 0.5 : 1, borderColor: "darkgray",
position: "absolute", opacity: d.type.includes("通") ? 0.5 : 1,
left: ((((width-50) / 100) * parseInt(formattedTime)) / 60) * 100, position: "absolute",
height: "100%", height: "100%",
}} },
onPress={() => openTrainInfo()} animatedStyle,
]}
> >
<View style={{ position: "relative" }}> <TouchableOpacity style={{ flex: 1 }} onPress={() => openTrainInfo()}>
<Text style={{ fontSize: 20, color: color }}>{formattedTime}</Text> <View style={{ position: "relative" }}>
<Text <Text style={{ fontSize: 20, color: color }}>{formattedTime}</Text>
style={{ <Text
fontSize: 10, style={{
position: "absolute", fontSize: 10,
bottom: -3, position: "absolute",
right: 0, bottom: -3,
fontWeight: "bold", right: 0,
}} fontWeight: "bold",
> }}
{d.type} >
</Text> {d.type}
</View> </Text>
<View style={{ flex: 1, flexDirection: "column" }}> </View>
<Text <View style={{ flex: 1, flexDirection: "column" }}>
style={{ <Text
fontSize: 10, style={{
flex: 1, fontSize: 10,
fontWeight: "bold", flex: 1,
}} fontWeight: "bold",
> }}
{trainName} >
</Text> {trainName}
</View> </Text>
</TouchableOpacity> </View>
</TouchableOpacity>
</Animated.View>
</View> </View>
); );
}; };