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 = useSharedValue(0)
開始するときはwithTiming
で360まで変化させます。
progress.value = withTiming(360, { duration: 6000 })
progress
の変化に応じて画面を変更するには、useDerivedValue
を使います。addArc
の3つ目の引数にprogress.value
を指定しています。
const path = useDerivedValue(() => {
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.value)
// ...
return path
}, [])
全体のコードは以下です。
export default function DynamicArc() {
const [animating, setAnimating] = useState(false)
const progress = useSharedValue(0)
const path = useDerivedValue(() => {
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.value)
path.lineTo(centerPos.x, centerPos.y).close()
return path
}, [])
const start = useCallback(() => {
if (animating) return
setAnimating(true)
progress.value = withTiming(360, { duration: 6000 }, () => {
setTimeout(() => {
progress.value = 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>
)
}
const styles = StyleSheet.create({
container: {
position: "relative",
},
canvas: {
flex: 1,
},
overlay: {
justifyContent: "center",
alignItems: "center",
},
startText: {
fontSize: 16,
fontWeight: "bold",
},
})
コード全体はGithubから確認可能です。