react-native-skiaで円弧を描く方法です。
path.addArc()
を使います。
const arcRect = {
x: centerPos.x - radius,
y: centerPos.y - radius,
width: radius * 2,
height: radius * 2,
}
path.addArc(arcRect, -90, 60)
最初の引数(oval
)に円全体の位置とサイズを指定するための矩形を設定します。
2つ目の引数(startAngle
)には円弧を開始する角度を指定します。角度は時計回りなので注意が必要です(下が90度、左が180度, 上が270度)。
3つ目の引数(sweepAngle
)は、開始角度からどれだけ円弧の角度を進めるかを指定します。
こちらはXamarinのドキュメントですがAPIの形式は同じであるため参考になると思います。 円弧を描画する 3 つの方法 - Xamarin | Microsoft Docs
以下は全体のコードです。
const path = Skia.Path.Make()
path.moveTo(centerPos.x, centerPos.y)
const arcRect = {
x: centerPos.x - radius,
y: centerPos.y - radius,
width: radius * 2,
height: radius * 2,
}
const startDegree = -90
path.addArc(arcRect, startDegree, 60)
path.lineTo(centerPos.x, centerPos.y).close()
return (
<Canvas style={[styles.canvas, { ...canvasSize }]}>
<Circle
cx={centerPos.x}
cy={centerPos.y}
r={radius}
color="lightblue"
style="stroke"
strokeWidth={2}
/>
<Path path={path} color="lightblue" style="fill" strokeWidth={2} />
</Canvas>
)
円弧の角度を増やしていくタイマーのようなアニメーションを実装してみます。STARTを押すと開始します。
まずアニメーションの進捗用のSkiaValueを用意します。この値を進捗に応じて0〜360まで変化させます。
const progress = useValue(0)
開始するときはrunTiming
で360まで変化させます。
runTiming(progress, 360, { duration: 6000 })
progress
の変化に応じて画面を変更するには、useComputedValue
を使います。addArc
の3つ目の引数にprogress.current
を指定しています。
const path = useComputedValue(() => {
const path = Skia.Path.Make()
// ...
const arcRect = {
x: centerPos.x - radius,
y: centerPos.y - radius,
width: radius * 2,
height: radius * 2,
}
const startDegree = -90
path.addArc(arcRect, startDegree, progress.current)
// ...
return path
}, [progress])
全体のコードは以下です。
export default function DynamicArc() {
const [animating, setAnimating] = useState(false)
const progress = useValue(0)
const path = useComputedValue(() => {
const path = Skia.Path.Make()
path.moveTo(centerPos.x, centerPos.y)
const arcRect = {
x: centerPos.x - radius,
y: centerPos.y - radius,
width: radius * 2,
height: radius * 2,
}
const startDegree = -90
path.addArc(arcRect, startDegree, progress.current)
path.lineTo(centerPos.x, centerPos.y).close()
return path
}, [progress])
const start = useCallback(() => {
if (animating) return
setAnimating(true)
runTiming(progress, 360, { duration: 6000 }, () => {
setTimeout(() => {
progress.current = 0
setAnimating(false)
}, 500)
})
}, [progress, animating])
return (
<View style={[styles.container, canvasSize]}>
<Canvas style={[styles.canvas, { ...canvasSize }]}>
<Circle
cx={centerPos.x}
cy={centerPos.y}
r={radius}
color="lightblue"
style="stroke"
strokeWidth={2}
/>
<Path path={path} color="lightblue" style="fill" strokeWidth={2} />
</Canvas>
{!animating && (
<TouchableOpacity
onPress={start}
style={[StyleSheet.absoluteFill, styles.overlay]}
>
<Text style={styles.startText}>START</Text>
</TouchableOpacity>
)}
</View>
)
}
コード全体はGithubから確認可能です。