You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使基于react-native-progress的React Native环形进度条在30秒后保持持续动画

Solution for Infinite Circular Progress Animation in React Native with react-native-progress

Got it, let's fix that circular progress bar to keep spinning non-stop! The core issue with your current code is that once the progress value hits 1, you clear the interval and halt the animation entirely. Here are two straightforward solutions to achieve infinite looping:


Option 1: Loop the Determinate Progress Animation

This approach keeps your custom progress increment logic but resets the progress value when it reaches 1, creating a seamless loop.

Step-by-Step Modifications:

  1. Update the animate function: Instead of stopping the interval when progress hits 1, reset the progress to 0 and continue the interval.
  2. Add cleanup in componentWillUnmount: Clear the interval when the component unmounts to prevent memory leaks.

Modified Core Animation Logic:

constructor() {
  super();
  this.state = {
    progress: 0,
    indeterminate: true,
    clipboardText: "",
    textInputText: "",
  };
  this.interval = null; // Store interval reference for cleanup
}

componentDidMount() {
  this.animate();
}

componentWillUnmount() {
  // Clean up interval to avoid memory leaks
  if (this.interval) {
    clearInterval(this.interval);
  }
}

animate() {
  let progress = 0;
  this.setState({ progress });
  setTimeout(() => {
    this.setState({ indeterminate: false });
    this.interval = setInterval(() => {
      progress += Math.random() / 30;
      // Reset progress to 0 when it exceeds 1 to loop the animation
      if (progress >= 1) {
        progress = 0;
      }
      this.setState({ progress });
    }, 600);
  }, 600);
}

Option 2: Use the Library's Built-in Indeterminate Mode

If you don't need the custom random progress increment logic, the react-native-progress library has a built-in infinite animation mode (indeterminate) that requires less code.

Step-by-Step Modifications:

  1. Keep indeterminate set to true in your state (no need to toggle it to false).
  2. Remove the custom animate function since the library handles the infinite animation automatically.

Simplified Core Logic:

constructor() {
  super();
  this.state = {
    progress: 0,
    indeterminate: true, // Keep this true for infinite animation
    clipboardText: "",
    textInputText: "",
  };
}

// No need for componentDidMount or animate function - the library handles the animation

You can still adjust the animation speed using the indeterminateAnimationDuration prop on Progress.Circle (you already have this set to 2000 in your code).


Full Modified Component Code (Using Option 1)

Here's your complete component with the looping determinate animation implemented:

import { Observer } from "mobx-react";
import React, { Component } 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";

const hRem = AppStore.screenHeight / 812;
const wRem = AppStore.screenWidth / 375;

export class OtpDetails extends Component {
  constructor() {
    super();
    this.state = {
      progress: 0,
      indeterminate: true,
      clipboardText: "",
      textInputText: "",
    };
    this.interval = null;
  }

  componentDidMount() {
    this.animate();
  }

  componentWillUnmount() {
    if (this.interval) {
      clearInterval(this.interval);
    }
  }

  animate() {
    let progress = 0;
    this.setState({ progress });
    setTimeout(() => {
      this.setState({ indeterminate: false });
      this.interval = setInterval(() => {
        progress += Math.random() / 30;
        if (progress >= 1) {
          progress = 0;
        }
        this.setState({ progress });
      }, 600);
    }, 600);
  }

  render() {
    const shadowOpt = {
      width: 160,
      height: 170,
      color: "#fff",
      border: 2,
      radius: 70,
      opacity: 6,
      x: 0,
      y: 3,
    };
    const { myTitle } = this.props.route.params;
    const setTextIntoClipboard = async () => {
      await Clipboard.setString(myTitle);
      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={() => {
                this.props.navigation.navigate("OtpScreen");
              }}
              >
                <Image style={styles.backButton} source={Iconpack.GLOBAL_BACK_BUTTON} />
              </TouchableOpacity>
              <Text style={styles.headerTitle}>{myTitle}</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={this.state.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 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={this.state.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]}>{myTitle}</Text>
              <TouchableOpacity onPress={() => {
                setTextIntoClipboard();
              }} 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: hRem * 56,
    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,
    lineHeight: hRem * 16,
    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,
  },
});

内容的提问来源于stack exchange,提问作者sourabh atigre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 02:42:38