react-nativeでPinterestみたいなUIを実現する
Pinterestの2カラムで高さ可変のUIをreact-nativeで作ります。
目標としたUI
このレイアウトはMansory
といいます。
できたUI
ライブラリ
Mansoryレイアウトを実現するJSのライブラリはいくつかあって、Packery、Mansory.js、Bricks.jsなどが有名です。しかしこれらはそのままreact-nativeでは使うことができないのでreact-native用のコンポーネントを探しました。
すると以下の2つが見つかりました。
まずスター数が多い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