2022-08-17

Skiaで円弧を描く

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

Loading Skia..

以下は全体のコードです。

  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を押すと開始します。

Loading Skia..

まずアニメーションの進捗用の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から確認可能です。

最終更新: 2025-04-07 11:42
筆者: @gaishimo 主にReact Nativeでのアプリ開発を専門に行っています。 React Nativeのお仕事お受けいたしますのでお気軽にご相談ください。
© 2025 Omoidasu, Inc. All rights reserved.