【React Native】ScrollViewでスライドビューワ擬きを作ろう

初投稿です。React Nativeでスライドビューワ的なものを作りました。よかったら見ていってください。

# 対象読者
この記事は最近React Nativeを使い始めた人向けの記事になります。私もその一人です。
React NativeのScrollViewの挙動に躓いた方のお役に立てればと思います。



# 作ったもの
スワイプで次のスライドや前のスライドに移動するというもの。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">React nativeでスライドビュー的なものを作りました <a href="https://t.co/nEW9XWtwjD">pic.twitter.com/nEW9XWtwjD</a></p>&mdash; けこ (@kekouhei) <a href="https://twitter.com/kekouhei/status/1201937941611696129?ref_src=twsrc%5Etfw">December 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

コードを載せたかったのですが長くなるので最後に…。

# 制作理由
こんなん探せばnpmに転がってるやろと思った方、その予想は正しいです。有志の方が製作してくださった素晴らしいコンポーネントの中には類似したものがいくつか見つかりました。

では何故基本的なコンポーネントでわざわざ作成しているのか？その理由は私がExpoを利用しているためです。Expoを利用するとiOSやAndroidでClientアプリを導入するだけで実機で動作テストができます。しかし、Expoを利用すると多くのネイティブコードを用いたパッケージ（react-native linkが必要なパッケージ）が利用できなくなったので自力で実装しました。

# 基礎コード
以下のコードを原型に、ScrollViewとViewを駆使して実装に挑戦しました。横向きのScrollViewにViewが3つ入っている簡単なコードです。Dimensionsで画面の大きさを取得してその値を元に子となるViewの大きさを決めているところがミソですね。

```react
import React from 'react';
import { StyleSheet, View, ScrollView, Dimensions } from 'react-native';
import { Input } from 'react-native-elements';

// 〜〜省略〜〜
render(){
  return(
    <ScrollView horizontal>
      <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
        <View style={styles.slideInsideContainer}>
          <Input />
        </View>
      </View>
      <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
        <View style={styles.slideInsideContainer}>
          <Input />
        </View>
      </View>
      <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
        <View style={styles.slideInsideContainer}>
          <Input />
        </View>
      </View>
    </ScrollView>
  );
}
// 〜〜省略〜〜

// スタイルシート
const styles = StyleSheet.create({
  slideOutsideContainer: (width) => ({
    width: width,
    height: 300,
    padding: 10,
  }),
  slideInsideContainer: {
    backgroundColor: '#FFFFFF',
    width: '100%',
    height: '100%',
    padding: 10,
    borderColor: '#000000',
    borderWidth: 1
  },
});
```

# ページング設定
先に紹介した動画のように**スワイプ毎にピシッと次のコンポーネントで移動をストップするため**に、ScrollViewのpagingEnabledというプロパティを利用しましょう。

pagingEnagleの値を1で設定するとAndroidで利用ができませんが**trueで設定するとAndroidでも動作します**。

```diff
import React from 'react';
import { StyleSheet, View, ScrollView, Dimensions } from 'react-native';
import { Input } from 'react-native-elements';

// 〜〜省略〜〜
render(){
  return(
-   <ScrollView horizontal>
+   <ScrollView horizontal pagingEnable={true}>
      <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
        <View style={styles.slideInsideContainer}>
          <Input />
        </View>
      </View>
      <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
        <View style={styles.slideInsideContainer}>
          <Input />
        </View>
      </View>
      <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
        <View style={styles.slideInsideContainer}>
          <Input />
        </View>
      </View>
    </ScrollView>
  );
}
// 〜〜省略〜〜

// スタイルシート
const styles = StyleSheet.create({
  slideOutsideContainer: (width) => ({
    width: width,
    height: 300,
    padding: 10,
  }),
  slideInsideContainer: {
    backgroundColor: '#FFFFFF',
    width: '100%',
    height: '100%',
    padding: 10,
    borderColor: '#000000',
    borderWidth: 1
  },
});
```

# 可変長にする
子となるViewの数が決まっているならこれで完成ですが時には可変長で作りたい場合もあると思います。
そういう時には、今3つ配置してあるViewを必要数格納した配列を渡してあげればおｋです。今回は仮に3個作る前提でforループとScrollViewの箇所に数値を書いていますが、実際に利用する際は変数にしてください。

```diff
import React from 'react';
import { StyleSheet, View, ScrollView, Dimensions } from 'react-native';
import { Input } from 'react-native-elements';

// 〜〜省略〜〜
var viewArray = [];

render(){
+ for(i = 0; i < 3; i++){
+   this.viewArray.push(
+     <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
+       <View style={styles.slideInsideContainer}>
+         <Input />
+       </View>
+     </View>
+   );
+ }

  return(
-   <ScrollView horizontal pagingEnable={true}>
+   <ScrollView horizontal pagingEnable={true} contentContainerStyle={{ width: 100 * 3 + '%' }}>
-     <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
-       <View style={styles.slideInsideContainer}>
-         <Input />
-       </View>
-     </View>
-     <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
-       <View style={styles.slideInsideContainer}>
-         <Input />
-       </View>
-     </View>
-     <View style={styles.slideOutsideContainer(Dimensions.get('window').width)}>
-       <View style={styles.slideInsideContainer}>
-         <Input />
-       </View>
-     </View>
+     {this.viewArray}
    </ScrollView>
  );
}
// 〜〜省略〜〜

// スタイルシート
const styles = StyleSheet.create({
  slideOutsideContainer: (width) => ({
    width: width,
    height: 300,
    padding: 10,
  }),
  slideInsideContainer: {
    backgroundColor: '#FFFFFF',
    width: '100%',
    height: '100%',
    padding: 10,
    borderColor: '#000000',
    borderWidth: 1
  },
});
```

# 最後に
アドバイス等コメントお待ちしております。
一応動作確認はしましたが、間違っている箇所等ありましたら指摘いただけると幸いです。