React Nativeで横スライドするページの作り方


React NativeのルーターライブラリのReact Navigationやreact-native-router-fluxを使えば、ページ遷移を再現することができる。またタブ切り替えだったり、スタック式に積み重ねるタイプのUIも備えている。しかし横スライドするような表現には向いていないように思う。

実際に構築しようとするならば、スクリーン数に応じてルーターを定義するか、ルーターパスを参照するようなことを思い及ぶ。しかしスライド数が可変だった場合の対応に困るし、都度定義するようだと運用を考慮するとナンセンスだ。

このエントリーでは、横スライドするページの作り方を紹介したい。

はじめに

横スライドするページとは、複数スクリーンにわたって横に遷移している様を表現しているコンテンツだ。たとえばアンケートページや回答ページなどに見られる造りで、スクリーン毎にアクションを起こしてもらうようなシーンに使われる。

作り方

まずはソースコードで確認していこう。

import React from 'react';
import { FlatList, Dimensions, View, Text, Button } from 'react-native';
import { Content } from 'native-base';
const { width } = Dimensions.get('window');
const data = {
  'results': [
    {
      'id': 1,
      'text': 'この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。',
    },
    {
      'id': 2,
      'text': 'この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。',
    },
    {
      'id': 3,
      'text': 'この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。',
    },
    {
      'id': 4,
      'text': 'この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。',
    },
    {
      'id': 5,
      'text': 'この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。',
    },
  ]
};
export default class WorkSheetScreen extends React.Component {
  constructor (props) {
    super(props);
    this.flatlist = {};
    this.content = [];
  }
  handleNextPress (index) {
    this.content[index]._root.scrollToPosition(0, 0, false);
    this.flatlist.scrollToIndex({
      index: index + 1,
    });
  }
  handlePrevPress (index) {
    this.content[index]._root.scrollToPosition(0, 0, false);
    this.flatlist.scrollToIndex({
      index: index - 1,
    });
  }
  render () {
    return (
      <FlatList
        ref={ ref => this.flatlist = ref }
        horizontal
        scrollEnabled={ false }
        keyExtractor={ item => `worksheet_${item.id}` }
        data={ data.results }
        renderItem={({ item, index }) => (
          <Content ref={ ref => this.content[index] = ref } style={{ width }}>
            <View>
              <Text>{ item.text }</Text>
            </View>
            <View>
              <Button
                title="Prev"
                onPress={() => this.handlePrevPress(index)}
                disabled={ index > 0 ? false : true }
              />
              <Button
                title="Next"
                onPress={() => this.handleNextPress(index)}
                disabled={ index < data.results.length - 1 ? false : true }
              />
            </View>
          </Content>
        )}
      />
    );
  }
}

上記は、横スライドするページに関するソースコードだが、見通しが悪くなる記述はすべて削除している。その点、容赦いただきたい。特にスタイルシートは一部を除いて、すべて削除しているから、脳内で良しなに置換しながら眺めてほしい。

さて前述では横スライドのことを「遷移している様」と表現しているが、実際には横スクロールで遷移している様を再現する。そのために必要なのが<FlatList/>だ。

<FlatList/>は、リストをレンダリングするためのUIコンポーネントで、クロスプラットフォームに対応している。

<FlatList/>data propsに与えた配列数に応じて、renderItem propsのコンポーネントが複数列レンダリングされる。

レンダリングされた要素には、keyExtractor propsでKeyを付与する。このときインデックス値だけを与えるのは悪手とされているから、なにかしらの文字列を含めると良いだろう。

そしてhorizontal propsによって横並びにする。

scrollEnabled propsfalseを与えると、スクロールを禁止できる。横スライドするページを表現するには必要な設定だろう。この場合、ボタンの押下によってスライドすることが考えられる。

FlatListコンポーネントのscrollToIndex({index: インデックス値})を実行するとインデックス値の要素がビュー領域にスクロールしてくるものだ。このscrollToIndex()メソッドにアクセスするためref propsで<FlatList/>への参照先を定義しなければならない。

あと、ひと工夫で、renderItem propsでレンダリングされるコンポーネントのトップレベル要素は、コンテンツ量に応じて縦スクロールするように<ScrollView/>などを配置したいところだ。サンプルソースコードの<Content/>は、<ScrollView/><SafeAreaView/>をラップしたものだ。

この縦スクロールの配慮で、ひと手間が発生する。たとえば縦長コンテンツの最下部にあるNextボタンで次ページに横スライドして、Prevボタンで前ページに横スライドすると、その時点のスクロールビューは最下部状態のままになっている。これは再読み込みということではないから仕方のないことなのだが、前ページに戻って最下部状態というのは、いささか不自然感が否めない。

これに対処するため、scrollToPosition(0, 0, false)の実行でカレントページを最上部にスクロールさせたうえで、横スクロールを実施したというわけだ。このscrollToPosition()メソッドにアクセスするため<Content/>にもref propsで参照先を定義している。

まとめ

横スライドするページの作り方の紹介だった。

scrollToIndex()について伝えたとおりインデックス値を与えれば、対象インデックスページにスライドする。この横スライドには、デフォルトでアニメーションが付与されている。アニメーションを廃止して俊敏にスライドしたい場合には、scrollToIndex({animated:false,index:0})って具合にanimatedプロパティが設けられている。スライド感を無くしたいときに使ってみても良さそうだ。

このエントリーが、あなたのクリエイティビティを刺激するものであると期待したい。


Leave a Reply

Your email address will not be published. Required fields are marked *