React Native Reanimated的 SVG圆形进度条的初始动画不起作用,但属性更新时动画会正常进行

移动开发 2026-07-08

我是React Native Reanimated的初学者。

我有一个使用Reanimated和 SVG的圆形进度组件。动画在组件渲染后,当 progress 属性改变时,可以正常工作。

然而在初始渲染时,如果 progress={40},圆圈会直接显示为40%,而不是从0% 动画到40%。

我尝试在不同的位置更新共享值,比如 useEffectuseAnimatedReactiononLayout,但无论我在哪里更新,圆圈仍然以最终值呈现,我看不到初始动画。

在一切就绪后再触发初始动画的正确方法是什么,这样首次渲染就能从0% 动画到目标进度值?

我的组件的完整代码:

import { View } from "react-native";
import Animated, {
  useAnimatedProps,
  useAnimatedReaction,
  useDerivedValue,
  useSharedValue,
  withTiming,
} from "react-native-reanimated";
import Svg, { Circle } from "react-native-svg";

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

export default function CircleProgress({ progress = 0 }: { progress: number }) {
  const size = useSharedValue(0);
  const radius = useDerivedValue(() => (size.value / 2) * 0.8); // 80% of half the container = leave room for strokeWidth
  const circumference = useDerivedValue(() => 2 * Math.PI * radius.value);
  const progressVal = useSharedValue(0);

  const offset = useDerivedValue(
    () => circumference.value * (1 - progressVal.value / 100),
  );

  useAnimatedReaction(
    () => circumference,
    (c, p) => {
      progressVal.set(progress);
    },
  );

  const barProps = useAnimatedProps(() => {
    return {
      strokeDashoffset: withTiming(offset.value),
      strokeDasharray: circumference.value,
      cx: size.value / 2,
      cy: size.value / 2,
      strokeWidth: size.value * 0.05,
      r: radius.value,
      originX: size.value / 2,
      originY: size.value / 2,
      rotation: -90,
    };
  });

  const trackProps = useAnimatedProps(() => ({
    cx: size.value / 2,
    cy: size.value / 2,
    strokeWidth: size.value * 0.05,
    r: radius.value,
  }));

  return (
    <View
      className="w-full aspect-square bg-amber-300"
      onLayout={(e) => {
        size.set(e.nativeEvent.layout.width);
      }}
    >
      <Svg width="100%" height="100%">
        {/* track */}
        <AnimatedCircle
          stroke="#e5e5e5"
          fill="none"
          animatedProps={trackProps}
        />
        {/* progress arc */}
        <AnimatedCircle
          stroke="blue"
          fill="none"
          strokeLinecap="round"
          animatedProps={barProps}
        />
      </Svg>
    </View>
  );
}

解决方案

我通过如下方式修改了SVG解决了这个问题:<Svg width="100%" height="100%" viewBox="0 0 100 100">,这样就不需要通过计算来设置宽度和高度,SVG本身会渲染到父容器。

import { useCallback, useEffect } from "react";
import { View } from "react-native";
import Animated, {
  Easing,
  useAnimatedProps,
  useDerivedValue,
  useSharedValue,
  withTiming,
} from "react-native-reanimated";
import Svg, { Circle } from "react-native-svg";
import { scheduleOnRN } from "react-native-worklets";

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

export default function CircleProgress({
  progress = 0,
  onCompleted,
}: {
  progress: number;
  onCompleted?: () => void;
}) {
  const progressVal = useSharedValue(0);

  const SIZE = 100;
  const STROKE_WIDTH = 5;
  const RADIUS = (SIZE - STROKE_WIDTH) / 2;
  const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
  const center = SIZE / 2;

  const onProgressCompleted = useCallback(() => {
    if (onCompleted) {
      onCompleted();
    }
  }, [onCompleted]);

  useEffect(() => {
    progressVal.set(
      withTiming(
        progress,
        { duration: 1000, easing: Easing.poly(3) },
        (done) => {
          if (done) {
            scheduleOnRN(onProgressCompleted);
          }
        },
      ),
    );
  }, [progress]);

  const offset = useDerivedValue(
    () => CIRCUMFERENCE * (1 - progressVal.value / 100),
  );

  const barProps = useAnimatedProps(() => {
    return {
      strokeDashoffset: offset.value,
    };
  });

  return (
    <View className="w-full aspect-square bg-red-100">
      <Svg width="100%" height="100%" viewBox="0 0 100 100">
        {/* track */}
        <AnimatedCircle
          cx={center}
          cy={center}
          strokeWidth={STROKE_WIDTH}
          r={RADIUS}
          stroke="#e5e5e5"
          fill="none"
        />
        {/* progress arc */}
        <AnimatedCircle
          cx={center}
          cy={center}
          strokeWidth={STROKE_WIDTH}
          r={RADIUS}
          strokeDasharray={CIRCUMFERENCE}
          strokeDashoffset={CIRCUMFERENCE}
          stroke="#22c55e"
          fill="none"
          strokeLinecap="round"
          animatedProps={barProps}
          transform={`rotate(-90 ${center} ${center})`}
        />
      </Svg>
    </View>
  );
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章