react-nativeでPinterestみたいなUIを実現する

Pinterestの2カラムで高さ可変のUIをreact-nativeで作ります。

目標としたUI

f:id:asmsuechan:20180903203039p:plain

このレイアウトはMansoryといいます。

できたUI

f:id:asmsuechan:20180903203900p:plain

ライブラリ

Mansoryレイアウトを実現するJSのライブラリはいくつかあって、PackeryMansory.jsBricks.jsなどが有名です。しかしこれらはそのままreact-nativeでは使うことができないのでreact-native用のコンポーネントを探しました。

すると以下の2つが見つかりました。

github.com

github.com

まずスター数が多いreact-native-masonryの方を試したのですが、これが結構曲者でなかなか期待通りに動いてくれないしバグってるしなんかオーナー修正するつもりなさそうだしで諦めました。

次に試したのがreact-native-masonry-layoutです。今回はこちらを使用しました。

これも正直スター数が少なかったりインタフェースが微妙だったりといろいろ不穏な雰囲気はあるのですが、動くのでよしとしました。

コード

実際に使ったコードはこちらです。

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, Image, TouchableHighlight, Dimensions} from 'react-native';
import Masonry from 'react-native-masonry-layout';

type Props = {};
export default class App extends Component<Props> {
  constructor() {
    super();

    this.state = {
    };
  }

  componentDidMount() {
    const { width } = Dimensions.get( "window" );
    const columnWidth = ( width - 10 ) / 2 - 10;
    const data2 = [
      {
        image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 500
      },
      {
        image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGYfU5N8lsJepQyoAigiijX8bcdpahei_XqRWBzZLbxcsuqtiH',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 600
      },
      {
        image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGYfU5N8lsJepQyoAigiijX8bcdpahei_XqRWBzZLbxcsuqtiH',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 700
      },
      {
        image: 'https://img.buzzfeed.com/buzzfeed-static/static/2017-03/30/12/asset/buzzfeed-prod-fastlane-01/sub-buzz-24597-1490890739-1.jpg',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 600
      },
      {
        image: 'https://img.buzzfeed.com/buzzfeed-static/static/2017-03/30/12/asset/buzzfeed-prod-fastlane-01/sub-buzz-24597-1490890739-1.jpg',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 300
      },
      {
        image: 'https://img.buzzfeed.com/buzzfeed-static/static/2017-03/30/12/asset/buzzfeed-prod-fastlane-01/sub-buzz-24597-1490890739-1.jpg',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 400
      },
      {
        image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 500
      },
      {
        image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC',
        text: 'wwwww',
        key: Math.random().toString(36).slice(-8),
        height: columnWidth / 400 * 500
      },
    ]
    this.refs.list.addItems( data2 );
  }

  render() {
    return (
      <View style={{ flex:1 }}>
        <View style={{flex: 1, flexGrow: 10, padding: 5}}>
          <Masonry
            ref="list"
            columns={2}
            renderItem={(item)=><View
                          style={{
                            margin: 5,
                            backgroundColor: "#fff",
                            borderRadius: 5,
                            overflow: "hidden",
                          }}>
              <Image source={{ uri: item.image }} style={{ height: item.height }}/>
              <Text>some text</Text>
            </View>}
          />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
});

環境

$ react-native --version
react-native-cli: 2.0.1
react-native: 0.56.0