あるSEのつぶやき・改

ITやシステム開発などの技術に関する話題を、取り上げたりしています。

React Native+Expoでドラッグ&ドロップ可能なリストを作成する方法

はじめに

React Native + Expo でドロップ&ドロップ可能なリスト(SortableなList)を作成する方法をご紹介します。

最初はreact-native-sortable-listなるものを試したのですが、ライブラリ内部で古い記述があり警告が消せないので断念しました。

github.com

次にreact-native-draggable-flatlistというライブラリを試したところ、まずまずよかったのでこのライブラリの記事を書きます。

github.com

なお、動作させるアプリは、Expo + NativeBase で土台まで作ってあるものになります。

ライブラリのインストールその前に

Expo のバージョンが 36.0.0 以上でないと、react-native-reanimatedというライブラリで警告が出てしまうので、まず Expo を 36.0.0 にアップグレードします。

$ expo upgrade 

ライブラリのインストール

ライブラリのインストールですが、yarnnpmではなくexpo installというコマンドを使うのに注意してください。おそらく、Expo 互換のバージョンをインストールするための仕組みだと思います。但し、アンインストールは、npm uninstall を使うのがよく分かりませんが。。

以下の2つのライブラリをインストールします。

$ expo install react-native-sortable-list
$ expo install react-native-reanimated

ライブラリの組み込み

Expo の画面にライブラリを組み込みます。

ほぼほぼサンプルの通りですが、関数型にしていることと、TypeScript にしているところがサンプルとの違いでしょうか。

import React, { useState } from 'react';
import {
  Container,
  Header,
  Title,
  Left,
  Icon,
  Right,
  Button,
  Body,
  Content,
} from 'native-base';
import { TouchableOpacity, Text, SafeAreaView } from 'react-native';
import DraggableFlatList from 'react-native-draggable-flatlist';

// 表示用データ
const exampleData = [...Array(20)].map((d, index) => ({
  key: `item-${index}`, // For example only -- don't use index as your key!
  label: index,
  backgroundColor: `rgb(${Math.floor(Math.random() * 255)}, ${index *
    5}, ${132})`,
}));


const HomeScreen: React.FunctionComponent = () => {
  const [data, setData] = useState(exampleData);

  const renderItem = ({ item, index, drag, isActive }) => {
    return (
      <TouchableOpacity
        style={{
          height: 100,
          backgroundColor: isActive ? 'blue' : item.backgroundColor,
          alignItems: 'center',
          justifyContent: 'center',
        }}
        onLongPress={drag}
      >
        <Text
          style={{
            fontWeight: 'bold',
            color: 'white',
            fontSize: 32,
          }}
        >
          {item.label}
        </Text>
      </TouchableOpacity>
    );
  };

  return (
    <Container>
      <Header>
        <Left>
            <Icon name="menu" />
          </Button>
        </Left>
        <Body>
          <Title>トップ画面</Title>
        </Body>
        <Right />
      </Header>

      <SafeAreaView style={{ flex: 1 }}>
        <DraggableFlatList
          data={data}
          renderItem={renderItem}
          keyExtractor={(item, index) => `draggable-item-${item.key}`}
          onDragEnd={({ data }) => setData(data)}
        />
      </SafeAreaView>
    </Container>
  );
};

export default HomeScreen;

これを動作させると以下のようになります。

  • iPhone

f:id:fnyablog:20200211115242g:plain:w320

  • Android

f:id:fnyablog:20200211115314g:plain:w320

はまったところ

Expo をアップグレードしないとライブラリの警告がでることもはまりましたが、下記の警告がでたのにかなりはまりました。

VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead.

ネットでも苦戦していたようです。

github.com

解決策は以下の StackOverflow にありました。

stackoverflow.com

結局のところ、ドラッグ&ドロップ可能な<FlatList><ScrollView>にネストするのがいけないようです。コンポーネントで展開されたあとに<ScrollView>になるものも同様ですね。

おわりに

React Native + Expo でドラッグ&ドロップ可能なリストは選択肢が少ないですが、まともに動作するものがあってよかったです。