現在時刻が表示されたり走行位置から列車時刻を更新したりアニメーションを強化した

This commit is contained in:
harukin-expo-dev-env
2025-08-27 17:12:34 +00:00
parent 35f1860b03
commit 92d37b7277
4 changed files with 278 additions and 141 deletions

View File

@@ -1,13 +1,29 @@
import { FC, useRef, useState, useCallback } from "react";
import { View, Text, ScrollView, useWindowDimensions } from "react-native";
import {
View,
Text,
ScrollView,
useWindowDimensions,
Vibration,
} from "react-native";
import { ExGridViewItem } from "./ExGridViewItem";
import Animated, {
useAnimatedStyle,
useSharedValue,
runOnJS,
useAnimatedScrollHandler,
withTiming,
Easing,
FadeIn,
FadeOut,
BounceInUp,
FadeInUp,
FadeOutUp,
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { ExGridViewTimePositionItem } from "./ExGridViewTimePositionItem";
import { useCurrentTrain } from "@/stateBox/useCurrentTrain";
import dayjs from "dayjs";
export const ExGridView: FC<{
data: {
@@ -18,21 +34,48 @@ export const ExGridView: FC<{
time: string;
}[];
}> = ({ data }) => {
const groupedData = {};
const groupKeys = [];
const groupedData: {
[d: number]: {
trainNumber: string;
array: string;
name: string;
type: string;
time: string;
isOperating: boolean;
}[];
} = {};
const groupKeys: string[] = [];
const { width } = useWindowDimensions();
const { currentTrain } = useCurrentTrain();
data.forEach((item) => {
const hour = item.time.split(":")[0];
let isOperating = false;
let [hour, minute] = item.time.split(":");
if (currentTrain.findIndex((x) => x.num == item.trainNumber) != -1) {
const currentTrainTime = currentTrain.find(
(x) => x.num == item.trainNumber
)?.delay;
if (currentTrainTime != "入線") {
[hour, minute] = dayjs()
.hour(parseInt(hour))
.minute(parseInt(minute))
.add(parseInt(currentTrainTime), "minute")
.format("H:m")
.split(":");
}
isOperating = true;
}
if (!groupedData[hour]) {
groupedData[hour] = [];
groupKeys.push(hour);
}
groupedData[hour].push(item);
groupedData[hour].push({ ...item, time: `${hour}:${minute}`, isOperating });
});
// ドラッグ位置を保持する共有値
const widthX = useSharedValue(width);
const savedWidthX = useSharedValue(width);
const isChanging = useSharedValue(false);
const animationProgress = useSharedValue(0); // アニメーション進行度
const [scrollEnabled, setScrollEnabled] = useState(true);
const scrollRef = useRef<Animated.ScrollView>(null);
@@ -55,21 +98,41 @@ export const ExGridView: FC<{
const gesture = Gesture.Pan()
.minPointers(2) // 最低2本指
.maxPointers(2) // 最大2本指
.onTouchesDown((e) => {
if (e.numberOfTouches >= 2) runOnJS(toggleScrollEnabled)(false);
.onStart(() => {
runOnJS(toggleScrollEnabled)(false);
})
.onTouchesUp((e) => {
runOnJS(toggleScrollEnabled)(true);
})
.onEnd((e) => {
.onEnd(() => {
runOnJS(toggleScrollEnabled)(true);
savedWidthX.value = widthX.value;
});
const longPressGesture = Gesture.Pan()
.minPointers(1)
.maxPointers(1)
.activateAfterLongPress(200)
.onStart(() => {
runOnJS(Vibration.vibrate)(30);
isChanging.value = true;
})
.onUpdate((e) => {
const calc = widthX.value + e.velocityY;
widthX.value = calc > width ? calc : width;
})
.onEnd(() => {
console.log("Long press ended");
isChanging.value = false;
});
// ジェスチャーを組み合わせる
const composed = Gesture.Simultaneous(pinchGesture, gesture);
const composed = Gesture.Simultaneous(
longPressGesture,
pinchGesture,
gesture
);
// アニメーションスタイル
const animatedStyle = useAnimatedStyle(() => ({
width: widthX.value,
backgroundColor: isChanging.value ? "#8adeffff" : "white",
}));
// 時ヘッダーを横にスクロールしたときの処理
const scrollX = useSharedValue(0);
@@ -81,7 +144,28 @@ export const ExGridView: FC<{
const stickyTextStyle = useAnimatedStyle(() => ({
transform: [{ translateX: scrollX.value }],
}));
const animatedLongPressStyle = useAnimatedStyle(() => ({
display: isChanging.value ? "flex" : "none",
}));
return (
<>
<Animated.View
style={[
{
position: "absolute",
width,
backgroundColor: "#26d1baff",
zIndex: 500,
top:0
},
animatedLongPressStyle,
]}
entering={FadeInUp} exiting={FadeOutUp}
>
<Text style={{ fontSize: 30, textAlign: "center", flex: 1 }}>
  
</Text>
</Animated.View>
<GestureDetector gesture={composed}>
<Animated.ScrollView
horizontal
@@ -122,7 +206,7 @@ export const ExGridView: FC<{
key={num}
style={{
flex: 1,
textAlign: "center",
textAlign: "left",
borderRightWidth: 0.5,
borderColor: "#ccc",
flexWrap: "nowrap",
@@ -136,7 +220,7 @@ export const ExGridView: FC<{
})}
<Text
style={{
textAlign: "center",
textAlign: "right",
borderRightWidth: 0.5,
borderColor: "#ccc",
flexWrap: "nowrap",
@@ -191,10 +275,12 @@ export const ExGridView: FC<{
array={array}
/>
))}
<ExGridViewTimePositionItem width={widthX} hour={hour} />
</View>,
])}
</Animated.ScrollView>
</Animated.ScrollView>
</GestureDetector>
</>
);
};

View File

@@ -26,6 +26,7 @@ export const ExGridViewItem: FC<{
name: string;
type: string;
time: string;
isOperating: boolean;
};
index: number;
width: SharedValue<number>;
@@ -235,7 +236,7 @@ export const ExGridViewItem: FC<{
>
<TouchableOpacity style={{ flex: 1 }} onPress={() => openTrainInfo()}>
<View style={{ position: "relative" }}>
<Text style={{ fontSize: 20, color: color, opacity: isSameTimeBefore ? 0 : 1 }}>{formattedTime}</Text>
<Text style={{ fontSize: 20, color: color, opacity: isSameTimeBefore ? 0 : 1, fontWeight:d.isOperating ? "bold" : "normal", fontStyle:d.isOperating? "italic" :"normal" }}>{formattedTime}</Text>
<Text
style={{
fontSize: 10,

View File

@@ -0,0 +1,44 @@
import { FC } from "react";
import { View } from "react-native";
import dayjs from "dayjs";
import { SharedValue, useAnimatedStyle } from "react-native-reanimated";
import Animated from "react-native-reanimated";
export const ExGridViewTimePositionItem: FC<{
width: SharedValue<number>;
hour: string;
}> = ({ width, hour }) => {
const date = dayjs();
const formattedTime = date.format("m");
const formattedHour = date.format("H");
// if(typeString == "回送"){
// return<></>;
// }
const animatedStyle = useAnimatedStyle(() => {
const leftPosition =
((((width.value - 50) / 100) * parseInt(formattedTime)) / 60) * 100;
return {
left: leftPosition,
};
}, [formattedTime]);
if (formattedHour != hour) return <></>;
return (
<View style={{ left: 0, height: 50, width: 1 }}>
<Animated.View
style={[
{
flexDirection: "column",
borderLeftWidth: 2,
//borderBottomWidth: 0.5,
borderStyle: "solid",
borderColor: "red",
position: "absolute",
height: "100%",
},
animatedStyle,
]}
/>
</View>
);
};

View File

@@ -50,7 +50,13 @@ export const StationDiagramView: FC<props> = ({ route }) => {
useEffect(() => {
if (allTrainDiagram && currentStation.length > 0) {
const stationName = currentStation[0].Station_JP;
let returnDataArray = [];
let returnDataArray: {
trainNumber: string;
array: any;
name: any;
type: any;
time: any;
}[] = [];
Object.keys(allTrainDiagram).forEach((d) => {
allTrainDiagram[d]
.split("#")