秒表计时器不准确,它比React Native Expo中的实时时间慢

问题描述 投票:0回答:1

我正在尝试在 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
    }
});
reactjs react-native timer expo expo-go
1个回答
0
投票

首先,您应该从

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>
...
)
© www.soinside.com 2019 - 2024. All rights reserved.