我正在尝试在 React Native Expo 中构建一个秒表,其中包含小时、分钟、秒和厘秒,这意味着 00:00:00:00,我遇到的问题是我在屏幕上看到的时间较慢例如,比谷歌的真正秒表它更慢或更快,但从来都不是正确的时间。例如,当谷歌秒表上的时间为 2 秒时,如果较慢,则在我的时间上为 1 秒,如果较快,则为 3 秒。在模拟器上,时间总是比较慢,所以我在 expo go 上进行测试,或者构建一个 apk 并在我的真实设备上进行测试。下面的代码在开始时给了我最接近的时间,然后当时间增加时,它变得比谷歌秒表更快。我需要你的帮助,请了解发生了什么事? setInterval() 的时间应该以毫秒为单位,但它没有给出正确的毫秒数,它是更慢还是更快。
编辑: 当我将间隔设置为 100 毫秒后,结果是正确的,但当我将其设置为 10 毫秒时,结果不准确
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Image, Modal, Platform } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { RadioButton } from 'react-native-paper';
import Ionicons from '@expo/vector-icons/Ionicons';
import { Video, ResizeMode } from 'expo-av';
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
export default function StopwatchScreen() {
const [menuVisible, setMenuVisible] = useState(false);
const [selectedOption, setSelectedOption] = useState(null);
const [checked, setChecked] = useState('first');
const radioOptions = ["Option 1", "Option 2", "Option 3"];
const [isRunning, setIsRunning] = useState(false);
const [time, setTime] = useState(0);
const [results, setResults] = useState([]);
const timer = useRef(null);
const handleLapButtonPress = useCallback(() => {
if(isRunning) {
setResults((previousResults) => [time, ...previousResults]);
} else {
setResults([]);
setTime(0);
}
}, [isRunning, time]);
const handleStartButtonPress = () => {
setIsRunning(!isRunning);
console.log("time: ", time);
console.log("isRunning: ", isRunning);
};
useEffect(() => {
let timer;
if(isRunning) {
timer = setInterval(timeStart, 20);
}
return () => clearInterval(timer);
}, [time, isRunning]);
const timeStart = () => {
setTime(prevStartTime => prevStartTime + 1);
console.log(time);
};
const toggleMenu = () => {
setMenuVisible(!menuVisible);
}
const padToTwo = (number) => (number <= 9 ? `0${number}` : number);
const displayTime = (milliseconds) => {
let hours = 0;
let minutes = 0;
let seconds = 0;
let centiseconds = 0;
let formatedMinutesSeconds;
let formatedCentiseconds;
//example 1000
if (milliseconds < 0) {
milliseconds = 0;
}
if (centiseconds < 100) {
formatedCentiseconds = `${padToTwo(centiseconds)}`;
}
centiseconds = Math.floor(milliseconds / 10) ;
let remainCentiseconds = centiseconds % 100;
seconds = Math.floor(milliseconds / 1000);
// millis = 55000;
// centiseconds = 5510;
// seconds = 55.1
if (seconds < 60) {
formatedCentiseconds = `${padToTwo(remainCentiseconds)}`;
formatedMinutesSeconds = `00:${padToTwo(seconds)}`;
} else {
formatedCentiseconds = `${padToTwo(remainCentiseconds)}`;
let remainSeconds = seconds % 60;
minutes = (seconds - remainSeconds) / 60;
if (minutes < 60) {
formatedMinutesSeconds = `${padToTwo(minutes)}:${padToTwo(remainSeconds)}`;
} else {
let remainMinutes = minutes % 60;
hours = (minutes - remainMinutes) / 60;
formatedMinutesSeconds = `${padToTwo(remainMinutes)}:${padToTwo(remainSeconds)}`;
}
}
return {
hours: padToTwo(hours),
minutesSeconds: formatedMinutesSeconds,
centiseconds: formatedCentiseconds,
};
};
return (
<View style={[styles.container, Platform.OS === 'android' && styles.androidPadding]}>
<View style={styles.header}>
<Text style={[styles.title, styles.textFont]}>Futuristic Stopwatch</Text>
<TouchableOpacity onPress={toggleMenu}>
<Image source={require('../assets/images/three_dots.png')} style={styles.dotsIcon} />
</TouchableOpacity>
</View>
<Modal
transparent={true}
animationType="slide"
visible={menuVisible}
onRequestClose={() => setMenuVisible(false)}
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<Ionicons name="close-circle" size={32} color="#11167F" onPress={() => setMenuVisible(false)} style={styles.closeButton} />
<View style={styles.sectionTitle}>
<Text style={[styles.sectionTitleText, styles.textFont]}>Theme</Text>
</View>
<View style={styles.radioBox}>
<View style={styles.radioOptions}>
<RadioButton
value="first"
status={ checked === 'first' ? 'checked' : 'unchecked' }
onPress={() => setChecked('first')}
color="blue"
/>
<Text style={[styles.textFont]}>Blue</Text>
</View>
<View style={styles.radioOptions}>
<RadioButton
value="second"
status={ checked === 'second' ? 'checked' : 'unchecked' }
onPress={() => setChecked('second')}
color="#DE382A"
/>
<Text style={[styles.textFont]}>Red</Text>
</View>
</View>
</View>
</View>
</Modal>
<View style={styles.contentContainer}>
<View style={styles.rowHud}>
{/* <Text style={[styles.timerOverlay, styles.textFont]}>00:00</Text> */}
<Text style={[styles.hoursTimerOverlay, styles.textFont]}>{`${displayTime(time).hours}`}</Text>
<Text style={[styles.timerOverlay, styles.textFont]}>{time}</Text>
<Text style={[styles.centisecondsTimerOverlay, styles.textFont]}>{`${displayTime(time).centiseconds}`}</Text>
</View>
<View style={styles.controls}>
<TouchableOpacity onPress={handleStartButtonPress} style={[styles.controlButtonBorder, { backgroundColor: isRunning ? "#340e0d" : "#0a2a12" }]}>
<View style={styles.controlButton}>
<Text style={{ color: isRunning ? "#ea4c49" : "#37d05c" }}>{isRunning ? 'Stop' : 'Start'}</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={handleLapButtonPress} style={[styles.controlButtonBorder, { backgroundColor: isRunning ? "#333333" : "#1c1c1e" }]}>
<View style={styles.controlButton}>
<Text style={{ color: isRunning ? "#fff" : "#9d9ca2" }}>{isRunning ? 'Lap' : 'Reset'}</Text>
</View>
</TouchableOpacity>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
padding: 0,
},
androidPadding: {
paddingTop: Platform.OS === 'android' ? 25 : 0,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 0,
paddingTop: 10,
paddingBottom: 10,
paddingLeft: 10,
paddingRight: 10,
backgroundColor: '#0a1055',
},
title: {
fontSize: 18,
//fontWeight: 'bold',
color: 'white',
},
dotsIcon: {
width: 20,
height: 20,
resizeMode: 'contain',
},
modalContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
textFont: {
fontFamily: 'Orbitron Black',
},
modalContent: {
backgroundColor: '#fff',
padding: 20,
borderRadius: 10,
elevation: 5,
},
closeButton: {
position: 'absolute',
top: 10,
right: 10,
},
sectionTitle: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 10,
},
sectionTitleText: {
fontFamily: 'Orbitron Black',
},
radioBox: {
marginTop: 20,
alignItems: 'flex-start',
width: 300,
},
radioOptions: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
contentContainer: {
flex: 1,
backgroundColor: '#0E1B84',
},
rowArrow: {
flex: 0.1,
flexDirection: 'row',
marginBottom: 0,
},
rowHud: {
flex: 0.5,
flexDirection: 'row',
marginBottom: 0
},
timerOverlay: {
position: 'absolute',
top: '50%', // Adjust as needed
left: '50%', // Adjust as needed
transform: [{ translateX: -90 }, { translateY: -30 }],
fontSize: 50,
color: 'white',
},
hoursTimerOverlay: {
position: 'absolute',
top: '35%', // Adjust as needed
left: '50%', // Adjust as needed
transform: [{ translateX: -30 }, { translateY: -30 }],
fontSize: 35,
color: 'white',
},
centisecondsTimerOverlay: {
position: 'absolute',
top: '71%', // Adjust as needed
left: '50%', // Adjust as needed
transform: [{ translateX: -30 }, { translateY: -30 }],
fontSize: 35,
color: 'white',
},
controls: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingLeft: 20,
paddingRight: 20,
},
controlButtonBorder: {
justifyContent: "center",
alignItems: "center",
width: 70,
height: 70,
borderRadius: 70,
},
controlButton: {
justifyContent: "center",
alignItems: "center",
width: 65,
height: 65,
borderRadius: 65,
borderColor: "#000",
borderWidth: 1
}
});
首先,您应该从
time
依赖项数组中删除 useEffect
。因为,在 setInterval
内部,您正在调用 setTime
函数,这会导致重新渲染。由于发生了重新渲染,useEffect 将运行并创建一个新的 setInterval,这就像一个无限循环。
仅当按下开始按钮时才应调用
setInterval
,按下停止按钮时应清除它。要清除按下按钮时的间隔,您可以将 setInterval 的返回值存储在 ref 中。
useEffect(() => {
if(isRunning) {
timerId.current = setInterval(timeStart, 10);
}
return () => clearInterval(timerId.current);
}, [isRunning]);
接下来,在
setInterval
中代替 20
作为延迟,使用 10
作为延迟,因为您也需要显示厘秒。 (1 厘秒 = 10 毫秒)
const handleStartButtonPress = () => {
setIsRunning(isRunning => {
if(isRunning && timerId.current){
clearTimeout(timerId.current)
}
return !isRunning
});
console.log("time: ", time);
console.log("isRunning: ", isRunning);
};
然后如果需要的话
displayTime
功能可以进一步简化:
// @param - centiSeconds refers to `time` stored in state
const displayTime = (centiSeconds) => {
let newCentiSeconds = centiSeconds % 100;
let seconds = Math.floor(centiSeconds / 100) % 60;
let minutes = Math.floor(seconds / 60) % 60;
let hours = Math.floor(minutes / 60 * 60) % 12;
return {
hours: hours.toString().padStart(2,0),
minutes: minutes.toString().padStart(2,0),
seconds: seconds.toString().padStart(2,0),
centiSeconds: newCentiSeconds.toString().padStart(2,0),
};
};
const {hours,minutes,seconds,centiSeconds} = displayTime(time)
return(
...
<Text style={[styles.hoursTimerOverlay, styles.textFont]}>{`${hours}`}</Text>
<Text style={[styles.timerOverlay, styles.textFont]}>:{`${minutes}`}</Text>
<Text style={[styles.timerOverlay, styles.textFont]}>:{`${seconds}`}</Text>
<Text style={[styles.centisecondsTimerOverlay, styles.textFont]}>:{`${centiSeconds}`}</Text>
...
)