如何使基于react-native-progress的React Native环形进度条在30秒后保持持续动画
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:
- Update the
animatefunction: Instead of stopping the interval when progress hits 1, reset the progress to 0 and continue the interval. - 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:
- Keep
indeterminateset totruein your state (no need to toggle it to false). - Remove the custom
animatefunction 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

