2022-08-30

react-native-skiaでSkeleton Screenを作成する

よくUIのローディング中の表示で使われるのがSkeleton Screenです。

Facebookなどで使われているローディングの名称を知ってる? / 調べたらたくさんありすぎた話 - omuriceman's blog

React Nativeで実装する場合 react-content-loaderreact-native-svg-animated-linear-gradient 等のライブラリを使う方法がありますが、これらは基本的にSVGを利用しています。今回はSkia(react-native-skia)を用いて同様のものを実装してみたいと思います。

前回の記事 でテキストにグラデーションを設定しグラデーションを変化させるアニメーションを実装しましたが、仕組み的には同じのため、同様の作りで実装してみたいと思います。

実装方法

グラデーションには<LinearGradient /> を使います。

<LinearGradient
  start={{ x: 0, y: 150 }}
  end={{ x: 300, y: 150 }}
  colors={[basicColor, accentColor, basicColor]}
/>

これをアニメーションで動的にグラデーションが移動するようにしていきます。実装の仕方は前回の記事 と同じですが、繰り返しアニメーションを指定したsharedValueを用意し(progress)、この値に応じてuseDerivedValue<LinearGradient />のポイント位置を動的に変化させます。

const progress = useSharedValue(0)

useEffect(() => {
  progress.value = withRepeat(
    withTiming(1, {
      easing: Easing.inOut(Easing.ease),
      duration: 2300,
    }),
    -1,
    false,
  )
}, [])

const positions = useDerivedValue(() => {
  return [
    interpolate(progress.value, [0, 0.25, 0.5, 0.75, 1], [0, 0, 0, 0.5, 1]),
    interpolate(progress.value, [0, 0.25, 0.5, 0.75, 1], [0, 0, 0.5, 1, 1]),
    interpolate(progress.value, [0, 0.25, 0.5, 0.75, 1], [0, 0.5, 1, 1, 1]),
  ]
}, [])
type Props = {
  basicColor: string
  accentColor: string
  canvasSize: { width: number; height: number }
  children: ReactNode
}

export function SkeletonViewContainer(props: Props) {
  const progress = useSharedValue(0)

  useEffect(() => {
    progress.value = withRepeat(
      withTiming(1, {
        easing: Easing.inOut(Easing.ease),
        duration: 2300,
      }),
      -1,
      false,
    )
  }, [])

  const positions = useDerivedValue(() => {
    return [
      interpolate(progress.value, [0, 0.25, 0.5, 0.75, 1], [0, 0, 0, 0.5, 1]),
      interpolate(progress.value, [0, 0.25, 0.5, 0.75, 1], [0, 0, 0.5, 1, 1]),
      interpolate(progress.value, [0, 0.25, 0.5, 0.75, 1], [0, 0.5, 1, 1, 1]),
    ]
  }, [])

  return (
    <Canvas style={props.canvasSize}>
      <Group>
        <LinearGradient
          start={{ x: 0, y: props.canvasSize.height % 2 }}
          end={{ x: props.canvasSize.width, y: props.canvasSize.height % 2 }}
          colors={[props.basicColor, props.accentColor, props.basicColor]}
          positions={positions}
        />
        {props.children}
      </Group>
    </Canvas>
  )
}

外側からは以下のようにコンテンツの中身をchildrenに指定してrenderさせます。

<SkeletonViewContainer
  basicColor="rgb(245, 245, 245)"
  accentColor="rgb(225, 225, 225)"
  canvasSize={canvasSize}
>
  <RoundedRect x={0} y={0} r={8} width={70} height={70} />
  <RoundedRect x={80} y={17} r={8} width={250} height={13} />
  <RoundedRect x={80} y={40} r={8} width={250} height={10} />
  <RoundedRect x={0} y={80} r={8} width={330} height={10} />
  <RoundedRect x={0} y={100} r={8} width={200} height={10} />
  <RoundedRect x={0} y={120} r={8} width={330} height={10} />
</SkeletonViewContainer>

実際の表示サンプル

以下のようにSkeleton Screenが表示されます!

Loading Skia..

全体のコード

こちら で確認できます。

最終更新: 2025-03-16 02:06
筆者: @gaishimo 主にReact Nativeでのアプリ開発を行っています。
© 2021 Omoidasu, Inc. All rights reserved.