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 = 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から確認可能です。

最終更新: 2022-09-11 23:40
筆者: @gaishimo 主にReact Nativeでのアプリ開発を行っています。
© 2021 Omoidasu, Inc. All rights reserved.