ペペロミア開発ブログ

React Nativeで開発しているアプリ「ペペロミア」の技術系記事を投稿してます

react-nativeでsortableなUIを実装

シェアフル Advent Calendar 2018 12日目の記事です。

webならドラッグ & ドロップでアイテム順番を入れ替えるけど、react-nativeならどうするのかなー と思いググったらreact-nativeでsortable listを実装しているライブラリがあったので、今回はそのライブラリを紹介します

sortableとは

js.studio-kingdom.com

ドラッグ & ドロップで要素を入れ替えるUIを指します。 順番入れ替えを実装する際に利用されるUIで、直感的な操作を提供できます

react-nativeでの実装

調べるといくつかライブラリが出てきますが、今回は以下のライブラリで実装してみた

github.com

選定理由は以下の通り

実装してみた

現状、こんな感じ

f:id:wheatandcat:20181212094406g:plain

並び替えに行くまでの導線が長いので、まだまだ改善の余地はあるけど、実装したかったことはできたので一旦良しとしよう

コード

ほぼExampleのコピペですが、大体こんな感じで動くコードが書けた。

https://github.com/wheatandcat/Shiori/blob/master/ShioriNative/src/components/organisms/SortableSchedule

■ Cards.tsx

import React, { Component } from "react";
import SortableList from "react-native-sortable-list";
import getKind from "../../../lib/getKind";
import Card from "../../molecules/Schedule/Card";
import Row from "./Row";

type DataKey = string | number;

export interface ItemProps {
  id: string;
  title: string;
  moveMinutes: number | null;
}

export interface Props {
  data: ItemProps[];
  onChange: (data: any) => void;
}

export interface RowProps {
  data: ItemProps;
  active: boolean;
}

export default class extends Component<Props> {
  renderItem({ data, active }: { data: ItemProps; active: boolean }) {
    return (
      <Row active={active}>
        <Card id={data.id} title={data.title} kind={getKind(data.title)} />
      </Row>
    );
  }

  onChange = (nextOrder: DataKey[]) => {
    const data = nextOrder.map(id => {
      return this.props.data.find(item => Number(item.id) === Number(id));
    });

    this.props.onChange(data);
  };

  render() {
    const obj = this.props.data.reduce((o, c) => ({ ...o, [c.id]: c }), {});

    return (
      <SortableList
        data={obj}
        renderRow={this.renderItem.bind(this)}
        style={{ flex: 1 }}
        onChangeOrder={this.onChange}
      />
    );
  }
}

■ Row.tsx

import React, { Component } from "react";
import { Animated, Easing, Platform, View } from "react-native";

export interface RowProps {
  active: boolean;
  children: any;
}

export default class extends Component<RowProps> {
  _active: any;
  _style: any;

  constructor(props: RowProps) {
    super(props);

    this._active = new Animated.Value(0);

    this._style = {
      ...Platform.select({
        ios: {
          transform: [
            {
              scale: this._active.interpolate({
                inputRange: [0, 1],
                outputRange: [1, 1.1]
              })
            }
          ],
          shadowRadius: this._active.interpolate({
            inputRange: [0, 1],
            outputRange: [2, 10]
          })
        },

        android: {
          transform: [
            {
              scale: this._active.interpolate({
                inputRange: [0, 1],
                outputRange: [1, 1.07]
              })
            }
          ],
          elevation: this._active.interpolate({
            inputRange: [0, 1],
            outputRange: [2, 6]
          })
        }
      })
    };
  }

  componentDidUpdate(prevProps: RowProps) {
    if (this.props.active !== prevProps.active) {
      Animated.timing(this._active, {
        duration: 300,
        easing: Easing.bounce,
        toValue: Number(this.props.active)
      }).start();
    }
  }

  render() {
    return (
      <Animated.View style={[this._style]}>
        <View style={{ paddingBottom: 50 }}>{this.props.children}</View>
      </Animated.View>
    );
  }
}

最後に

ドラッグ & ドロップはreact-dndという魔境ライブラリの思い出があったので手を出していなかったけど、「react-native-sortable-list」は使い方が限定されているので、実装しやすかった。

github.com