React Native Reanimated的 SVG圆形进度条的初始动画不起作用,但属性更新时动画会正常进行
我是React Native Reanimated的初学者。
我有一个使用Reanimated和 SVG的圆形进度组件。动画在组件渲染后,当 progress 属性改变时,可以正常工作。
然而在初始渲染时,如果 progress={40},圆圈会直接显示为40%,而不是从0% 动画到40%。
我尝试在不同的位置更新共享值,比如 useEffect、useAnimatedReaction 和 onLayout,但无论我在哪里更新,圆圈仍然以最终值呈现,我看不到初始动画。
在一切就绪后再触发初始动画的正确方法是什么,这样首次渲染就能从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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。