我想在 30 秒后重新启动一个圆形进度条

Posted

技术标签:

【中文标题】我想在 30 秒后重新启动一个圆形进度条【英文标题】:I want to restart a circular progress bar after 30 seconds 【发布时间】:2022-01-02 23:07:20 【问题描述】:
const useProgress = (maxTimeInSeconds = 30) => 
    const [elapsedTime, setElapsedTime] = useState(0);
    const [progress, setProgress] = useState(0);

    useEffect(() => 
      const intervalId = setInterval((callback) => 
        if (progress < 1) 
          setElapsedTime((t) => t + 1);
        
      , 1000);

      return () => clearInterval(intervalId);
    , []);

    useEffect(() => 
      setProgress(elapsedTime / maxTimeInSeconds);
      console.log(elapsedTime);
    , [elapsedTime]);

    return progress;
  ;
  const progress = useProgress();

在这里,我实现了将圆形进度条移动 30 秒的逻辑。我想要做的是在 30 秒后重新开始进度并保持循环 30 秒。

这里我已经粘贴了整个代码

import  Observer  from "mobx-react";
import React,  Component, useEffect, useState  from "react";
import 
  StyleSheet,
  Text,
  View,
  ImageBackground,
  TouchableOpacity,
  Image,
  Platform,
  Dimensions,
  Clipboard,
 from "react-native";
import * as Progress from "react-native-progress";
import AppStore from "../../stores/AppStore";
import Iconpack from "../../utils/Iconpack";
import Theme from "../../utils/Theme";
import DeviceInfo from "react-native-device-info";
import CircularProgressBar from "./CircularProgressBar";
import  Shadow  from "react-native-shadow-2";
import Toast from "react-native-simple-toast";
import  BoxShadow  from "react-native-shadow";
import FastImage from "react-native-fast-image";
// import Clipboard from "@react-native-community/clipboard";
const hRem = AppStore.screenHeight / 812;
const wRem = AppStore.screenWidth / 375;

const OtpDetails = ( route, params, navigation ) => 
  const  id, title  = route.params;
  const useProgress = (maxTimeInSeconds = 30) => 
    const [elapsedTime, setElapsedTime] = useState(0);
    const [progress, setProgress] = useState(0);

    useEffect(() => 
      const intervalId = setInterval((callback) => 
        if (progress < 1) 
          setElapsedTime((t) => t + 1);
        
      , 1000);

      return () => clearInterval(intervalId);
    , []);

    useEffect(() => 
      console.log(elapsedTime);
      setProgress(elapsedTime / maxTimeInSeconds);
      if (elapsedTime == 30) 
        setProgress(elapsedTime / maxTimeInSeconds);
      
    , [elapsedTime / maxTimeInSeconds]);

    return progress;
  ;
  const progress = useProgress();

  const setTextIntoClipboard = async () => 
    await Clipboard.setString(title);
    Toast.show("OTP copied successfully!", 200);
  ;
  const deviceName = DeviceInfo.getModel();
  return (
    <Observer>
      () => (
        <View style= flex: 1 >
          <FastImage
            style=styles.container
            source=Iconpack.GLOBAL_BG
            resizeMode=FastImage.resizeMode.cover
          />
          <View style=styles.headerStyle>
            <TouchableOpacity
              onPress=() => 
                navigation.navigate("OtpScreen");
              
            >
              <Image
                style=styles.backButton
                source=Iconpack.GLOBAL_BACK_BUTTON
              />
            </TouchableOpacity>
            <Text style=styles.headerTitle>title</Text>
            <View style= flexDirection: "row" >
              <TouchableOpacity>
                <Image
                  style=[styles.editTrashButton,  marginRight: 19 ]
                  source=Iconpack.EDIT_ICON
                />
              </TouchableOpacity>
              <TouchableOpacity>
                <Image
                  style=styles.editTrashButton
                  source=Iconpack.DELETE_ICON
                />
              </TouchableOpacity>
            </View>
          </View>

          <View style=styles.circleOuter>
            Platform.OS === "ios" ? (
              <View style=styles.circleContainer>
                <Progress.Circle
                  style= alignItems: "center" 
                  progress=progress
                  // indeterminate=this.state.indeterminate
                  size=Dimensions.get("window").width * 0.561
                  thickness=10
                  borderWidth=0
                  endAngle=10
                  strokeCap="round"
                  color="#354659"
                  // indeterminateAnimationDuration=1
                  unfilledColor="#031830"
                />
              </View>
            ) : (
              <Shadow
                distance=20
                startColor="rgba(27, 100, 206, 0.5)"
                radius=210
                safeRender=true
              >
                <View style=styles.circleContainer>
                  <Progress.Circle
                    style= alignItems: "center", overflow: "hidden" 
                    progress=progress
                    // indeterminate=this.state.indeterminate
                    size=Dimensions.get("window").width * 0.561
                    thickness=10
                    borderWidth=0
                    endAngle=10
                    strokeCap="round"
                    color="#354659"
                    indeterminateAnimationDuration=2000
                    unfilledColor="#031830"
                  />
                </View>
              </Shadow>
            )

            <Text style=[styles.circleItem]>title</Text>
            <TouchableOpacity
              onPress=() => 
                setTextIntoClipboard();
              
              style=styles.copyItem
            >
              <Text style=styles.copyText>TAP TO COPY</Text>
            </TouchableOpacity>
          </View>

          /* <View style=styles.circleOuter>
              <View style=styles.circleContainer>
                <Text style=styles.circleItem>title</Text>
              </View>
              <TouchableOpacity
                onPress=() => 
                  copyToClipboard();
                
                style=styles.copyItem
              >
                <Text style=styles.copyText>TAP TO COPY</Text>
              </TouchableOpacity> */
          /* </View> */
        </View>
      )
    </Observer>
  );
;

export default OtpDetails;

const styles = StyleSheet.create(
  container: 
    flex: 1,
    position: "absolute",
    top: 0,
    right: 0,
    left: 0,
    bottom: 0,
    backgroundColor: "#021831",
  ,
  headerStyle: 
    flexDirection: "row",
    marginTop: Platform.OS === "ios" ? 56 : 40,
    marginHorizontal: wRem * 26.66,
    justifyContent: "space-between",
    alignItems: "center",
  ,
  backButton: 
    width: wRem * 12,
    height: hRem * 21,
  ,
  editTrashButton: 
    width: wRem * 23,
    height: hRem * 23,
  ,
  headerTitle: 
    ...Theme.encodeSansMed2,
    color: "#FFFFFF",
    fontWeight: "600",
    marginLeft: wRem * 50,
  ,
  circleOuter: 
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  ,
  circleContainer: 
    borderRadius:
      Math.round(
        Dimensions.get("window").width + Dimensions.get("window").height
      ) / 2,
    width: Dimensions.get("window").width * 0.56,
    height: Dimensions.get("window").width * 0.56,
    justifyContent: "center",
    backgroundColor: "black",
    shadowColor: "rgba(27, 100, 206, 0.5)",
    shadowOffset: 
      width: 0,
      height: 0,
    ,
    shadowOpacity: 30,
    shadowRadius: 30,
  ,
  circleItem: 
    color: "white",
    ...Theme.encodeSansMed7,
    textAlign: "center",
    position: "absolute",
    width: "100%",
    paddingBottom: 100,
  ,
  copyItem: 
    justifyContent: "center",
    alignItems: "center",
    marginTop: hRem * 64,
  ,
  copyText: 
    color: "#3D4756",
    ...Theme.encodeSansMed4,
  ,
);

所有样式,最重要的是我正在使用第三方库 react-native-progress 谢谢

【问题讨论】:

如果你正在编写一个 TOTP 应用程序,视觉组件不应该由代码到期的实际时间驱动,而不是......相反吗? 【参考方案1】:

progress 不需要是状态原子,因为它总是可以从经过的时间和最大时间明确地计算出来。

使用模(余数)运算符,您可以让观察到的进度在最长时间后始终循环回 0。

const useProgress = (maxTimeInSeconds = 30) => 
    const [elapsedTime, setElapsedTime] = useState(0);
        useEffect(() => 
            const intervalId = setInterval(() => 
                setElapsedTime((t) => t + 1);
            , 1000);
        return () => clearInterval(intervalId);
    , []);
    return (elapsedTime % maxTimeInSeconds) / maxTimeInSeconds;
;

const progress = useProgress();

或者,如果您想要更准确的基于挂钟的计时:

const useProgress = (maxTimeInSeconds = 30) => 
    const [initialTime] = useState(() => +new Date());
    const [counter, setCounter] = useState(0);
    useEffect(() => 
        const intervalId = setInterval(() => 
            setCounter((t) => t + 1);
        , 1000);
        return () => clearInterval(intervalId);
    , []);

    const elapsedTime = Math.floor(((+new Date()) - initialTime) / 1000);
    if (elapsedTime == 30) 
        return maxTimeInSeconds;
    
    
    return (elapsedTime % maxTimeInSeconds) / maxTimeInSeconds;
;

这会将进度开始锁定到组件的挂载时间,即使setInterval 不准确,也应该可以更好地工作。

【讨论】:

非常感谢,但是当它达到 30 秒时它会回来并重新开始我想要的是,它会继续顺时针移动@AkX 什么会顺时针移动?这里没有视觉元素。 请找到整个代码 非常感谢您的回复,但有一个问题,进度条从 0 开始,到 28 秒结束,然后又回来,根本没有达到 30 秒@AKX 如果这是一个 (T)OTP 应用程序,无论如何你都不应该依赖setInterval - 不能保证它会以 1000 毫秒的间隔运行。

以上是关于我想在 30 秒后重新启动一个圆形进度条的主要内容,如果未能解决你的问题,请参考以下文章

圆形进度条 Android

角圆形 svg 进度条渐变笔画

圆形百分比进度条

我如何在我的异步任务类中使用圆形旋转进度条

iOS 制作个圆形进度条

如何在kivy中制作圆形进度条?