Skip to content

Commit efb97d4

Browse files
authored
Update to V0.4.0 (#47)
* Fix scrolling issue, and use flat-list for performance gain * Add key extractor * Remove extraData property * Add custom-image property * Update example using react-native-fast-image * Abstract logic into own generic component * Update test * Update deps and test * Remove node v5 from travis * Clean up Masonry * Bump ver * Update column and test * Update readme.md * Update readme.md * Update readme.md * Update readme.md
1 parent 2eb9b9e commit efb97d4

16 files changed

Lines changed: 247 additions & 101 deletions

File tree

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@ language: node_js
22
node_js:
33
- v7
44
- v6
5-
- v5

__tests__/__snapshots__/masonry.test.js.snap

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
exports[`SNAPSHOT: All functionality should match prev snapshot 1`] = `
44
<View
55
onLayout={[Function]}
6+
style={
7+
Object {
8+
"flex": 1,
9+
}
10+
}
611
>
712
<RCTScrollView
813
contentContainerStyle={
914
Object {
10-
"flex": 1,
1115
"flexDirection": "row",
1216
"justifyContent": "space-between",
1317
"width": "100%",

__tests__/brick.test.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,9 @@ test('Render Brick', () => {
2626

2727
test('PRIVATE FUNC: Renders image tag', () => {
2828
const imageTag = _getImageTag(mock[0], 9);
29-
expect(imageTag).toEqual(
30-
<Image
31-
key='http://test1.jpg'
32-
source={{ uri: 'http://test1.jpg' }}
33-
resizeMethod="auto"
34-
style={{ width: 100, height: 200, marginTop: 9 }} />);
29+
const imageTree = renderer.create(imageTag).toJSON();
30+
expect(imageTree.type).toEqual('Image');
31+
expect(imageTree.props.source.uri).toEqual('http://test1.jpg');
3532
});
3633

3734
test('PRIVATE FUNC: Renders touchable tag properly', () => {

__tests__/masonry.test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ test('Render masonry correct', () => {
2222

2323
// Crucial styles for the grid to work
2424
expect(scrollView.props.contentContainerStyle).toEqual({
25-
flex: 1,
2625
justifyContent: 'space-between',
2726
flexDirection: 'row',
2827
width: '100%'

components/Brick.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { Component } from 'react';
22
import { View, Image, TouchableHighlight } from 'react-native';
3+
import Injector from 'react-native-injectable-component';
34

45
export default function Brick (props) {
56
// Avoid margins for first element
@@ -17,14 +18,29 @@ export default function Brick (props) {
1718
}
1819

1920
// _getImageTag :: Image, Gutter -> ImageTag
20-
export function _getImageTag (image, gutter = 0) {
21+
export function _getImageTag (props, gutter = 0) {
22+
const imageProps = {
23+
key: props.uri,
24+
source: {
25+
uri: props.uri
26+
},
27+
resizeMethod: 'auto',
28+
style: {
29+
...props.imageContainerStyle,
30+
31+
width: props.width,
32+
height: props.height,
33+
marginTop: gutter,
34+
}
35+
};
36+
2137
return (
22-
<Image
23-
key={image.uri}
24-
source={{ uri: image.uri }}
25-
resizeMethod='auto'
26-
style={{ width: image.width, height: image.height, marginTop: gutter, ...image.imageContainerStyle }} />
27-
);
38+
<Injector
39+
defaultComponent={Image}
40+
defaultProps={imageProps}
41+
injectant={props.customImageComponent}
42+
injectantProps={props.customImageProps} />
43+
)
2844
}
2945

3046
// _getTouchableUnit :: Image, Number -> TouchableTag

components/Column.js

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { Component } from 'react';
2-
import { View, Image, TouchableHighlight } from 'react-native';
2+
import { View, Image, TouchableHighlight, FlatList } from 'react-native';
33
import styles from '../styles/main';
44
import PropTypes from 'prop-types';
55
import Brick from './Brick';
@@ -12,6 +12,12 @@ export default class Column extends Component {
1212
parentDimensions: PropTypes.object,
1313
columnKey: PropTypes.string,
1414
imageContainerStyle: PropTypes.object,
15+
customImageComponent: PropTypes.func,
16+
customImageProps: PropTypes.object
17+
};
18+
19+
static defaultProps = {
20+
imageContainerStyle: {}
1521
};
1622

1723
constructor(props) {
@@ -53,7 +59,7 @@ export default class Column extends Component {
5359

5460
// Resize image while maintain aspect ratio
5561
// _resizeByColumns :: ImgDimensions , parentDimensions, nColumns -> AdjustedDimensions
56-
_resizeByColumns (imgDimensions, parentDimensions, nColumns=2) {
62+
_resizeByColumns (imgDimensions = { width: 0, height: 0 }, parentDimensions, nColumns=2) {
5763
const { height, width } = parentDimensions;
5864

5965
// The gutter is 1% of the available view width
@@ -78,30 +84,54 @@ export default class Column extends Component {
7884
}
7985

8086
// Renders the "bricks" within the columns
81-
// _renderBricks :: [images] -> [TouchableTag || ImageTag...]
82-
_renderBricks (bricks) {
83-
return bricks.map((brick, index) => {
84-
const gutter = (index === 0) ? 0 : brick.gutter;
85-
const key = `RN-MASONRY-BRICK-${brick.column}-${index}`;
86-
const { imageContainerStyle } = this.props;
87-
const props = { ...brick, gutter, key, imageContainerStyle };
87+
// _renderBrick :: images -> [TouchableTag || ImageTag...]
88+
_renderBrick = (data) => {
89+
// Example Data Structure
90+
// {
91+
// "item": {
92+
// "uri": "https://img.buzzfeed.com/buzzfeed-static/static/2016-01/14/20/campaign_images/webdr15/which-delicious-mexican-food-item-are-you-based-o-2-20324-1452822970-1_dblbig.jpg",
93+
// "column": 0,
94+
// "dimensions": {
95+
// "width": 625,
96+
// "height": 415
97+
// },
98+
// "width": 180.675,
99+
// "height": 119.96820000000001,
100+
// "gutter": 3.65
101+
// },
102+
// "index": 9
103+
// }
104+
const brick = data.item;
105+
const gutter = (data.index === 0) ? 0 : brick.gutter;
106+
const key = `RN-MASONRY-BRICK-${brick.column}-${data.index}`;
107+
const { imageContainerStyle, customImageComponent, customImageProps } = this.props;
108+
const props = { ...brick, gutter, key, imageContainerStyle, customImageComponent, customImageProps };
88109

89110
return (
90111
<Brick
91112
{...props} />
92113
);
93-
});
94114
}
115+
116+
// _keyExtractor :: item -> id
117+
_keyExtractor = (item) => (item.id || item.key);
95118

96119
render() {
97120
return (
98121
<View
99-
key={this.props.columnKey}
100122
style={[
101-
{ width: this.state.columnWidth },
123+
{
124+
width: this.state.columnWidth,
125+
overflow: 'hidden'
126+
},
102127
styles.masonry__column
103128
]}>
104-
{this._renderBricks(this.state.images)}
129+
<FlatList
130+
key={this.props.columnKey}
131+
data={this.state.images}
132+
keyExtractor={this._keyExtractor}
133+
renderItem={this._renderBrick}
134+
/>
105135
</View>
106136
)
107137
}

components/Masonry.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export default class Masonry extends Component {
2424
columns: PropTypes.number,
2525
sorted: PropTypes.bool,
2626
imageContainerStyle: PropTypes.object,
27+
customImageComponent: PropTypes.func,
28+
customImageProps: PropTypes.object
2729
};
2830

2931
static defaultProps = {
@@ -80,18 +82,18 @@ export default class Masonry extends Component {
8082
.map((brick, index) => assignObjectIndex(index, brick))
8183
.map(brick => resolveImage(brick))
8284
.map(resolveTask => resolveTask.fork(
83-
(err) => console.warn('Image failed to load'),
84-
(resolvedBrick) => {
85-
this.setState(state => {
86-
const sortedData = _insertIntoColumn(resolvedBrick, state._sortedData, this.props.sorted);
87-
88-
return {
89-
dataSource: state.dataSource.cloneWithRows(sortedData),
90-
_sortedData: sortedData,
91-
_resolvedData: [...state._resolvedData, resolvedBrick]
92-
}
93-
});;
94-
}));
85+
(err) => console.warn('Image failed to load'),
86+
(resolvedBrick) => {
87+
this.setState(state => {
88+
const sortedData = _insertIntoColumn(resolvedBrick, state._sortedData, this.props.sorted);
89+
90+
return {
91+
dataSource: state.dataSource.cloneWithRows(sortedData),
92+
_sortedData: sortedData,
93+
_resolvedData: [...state._resolvedData, resolvedBrick]
94+
}
95+
});;
96+
}));
9597
}
9698

9799
_setParentDimensions(event) {
@@ -107,7 +109,7 @@ export default class Masonry extends Component {
107109

108110
render() {
109111
return (
110-
<View onLayout={(event) => this._setParentDimensions(event)}>
112+
<View style={{flex: 1}} onLayout={(event) => this._setParentDimensions(event)}>
111113
<ListView
112114
contentContainerStyle={styles.masonry__container}
113115
dataSource={this.state.dataSource}
@@ -118,6 +120,8 @@ export default class Masonry extends Component {
118120
columns={this.props.columns}
119121
parentDimensions={this.state.dimensions}
120122
imageContainerStyle={this.props.imageContainerStyle}
123+
customImageComponent={this.props.customImageComponent}
124+
customImageProps={this.props.customImageProps}
121125
key={`RN-MASONRY-COLUMN-${rowID}`}/> }
122126
/>
123127
</View>
@@ -134,12 +138,12 @@ export function _insertIntoColumn (resolvedBrick, dataSet, sorted) {
134138

135139
if (column) {
136140
// Append to existing "row"/"column"
137-
const bricks = [...column, resolvedBrick]
138-
if(sorted) {
141+
const bricks = [...column, resolvedBrick];
142+
if (sorted) {
139143
// Sort bricks according to the index of their original array position
140-
bricks = bricks.sort((a, b) => { return (a.index < b.index) ? -1 : 1; });
144+
bricks = bricks.sort((a, b) => (a.index < b.index) ? -1 : 1);
141145
}
142-
dataCopy[columnIndex] = bricks
146+
dataCopy[columnIndex] = bricks;
143147
} else {
144148
// Pass it as a new "row" for the data source
145149
dataCopy = [...dataCopy, [resolvedBrick]];

example/App/Container/app.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Image,
1515
Slider
1616
} from 'react-native';
17+
import FastImage from 'react-native-fast-image';
1718
import Masonry from 'react-native-masonry';
1819

1920
// list of images
@@ -45,10 +46,10 @@ const data = [
4546
}
4647
},
4748
{
48-
uri: 'https://s-media-cache-ak0.pinimg.com/736x/b1/21/df/b121df29b41b771d6610dba71834e512.jpg'
49+
uri: 'https://s-media-cache-ak0.pinimg.com/736x/b1/21/df/b121df29b41b771d6610dba71834e512.jpg',
4950
},
5051
{
51-
uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTQpD8mz-2Wwix8hHbGgR-mCFQVFTF7TF7hU05BxwLVO1PS5j-rZA'
52+
uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTQpD8mz-2Wwix8hHbGgR-mCFQVFTF7TF7hU05BxwLVO1PS5j-rZA',
5253
},
5354
{
5455
uri: 'https://s-media-cache-ak0.pinimg.com/736x/5a/15/0c/5a150cf9d5a825c8b5871eefbeda8d14.jpg'
@@ -117,11 +118,11 @@ export default class example extends Component {
117118

118119
render() {
119120
return (
120-
<ScrollView style={{backgroundColor: '#f4f4f4'}}>
121-
<View style={styles.center}>
121+
<View style={{flex: 1, backgroundColor: '#f4f4f4'}}>
122+
<View style={[styles.center, styles.header]}>
122123
<Text style={{ fontWeight: '800', fontSize: 20 }}>Masonry Demo</Text>
123124
</View>
124-
<View style={[styles.center, { marginTop: 10, marginBottom: 25 }]}>
125+
<View style={[styles.center, styles.buttonGroup, { marginTop: 10, marginBottom: 25 }]}>
125126
<TouchableHighlight style={styles.button} onPress={() => this.setState({ columns: 2 })}>
126127
<Text>2 Column</Text>
127128
</TouchableHighlight>
@@ -135,7 +136,7 @@ export default class example extends Component {
135136
<Text>9 Columns</Text>
136137
</TouchableHighlight>
137138
</View>
138-
<View style={[styles.center, { marginTop: 10, marginBottom: 25, flexDirection: 'column'}]}>
139+
<View style={[styles.center, styles.slider, { marginTop: 10, marginBottom: 25, flexDirection: 'column'}]}>
139140
<View style={{paddingLeft: 10}}>
140141
<Text>Dynamically adjust padding: {this.state.padding}</Text>
141142
</View>
@@ -148,13 +149,14 @@ export default class example extends Component {
148149
onValueChange={(value) => this.setState({padding: value})} />
149150
</View>
150151
</View>
151-
<View style={{height: '100%', padding: this.state.padding}}>
152+
<View style={{flex: 1, flexGrow: 10, padding: this.state.padding}}>
152153
<Masonry
153154
sorted
154155
bricks={data}
155-
columns={this.state.columns}/>
156+
columns={this.state.columns}
157+
customImageComponent={FastImage} />
156158
</View>
157-
</ScrollView>
159+
</View>
158160
);
159161
}
160162
}
@@ -164,7 +166,17 @@ const styles = StyleSheet.create({
164166
justifyContent: 'center',
165167
alignItems: 'center',
166168
backgroundColor: '#f4f4f4',
167-
flex: 1
169+
flex: 1,
170+
flexBasis: '10%'
171+
},
172+
header: {
173+
flexGrow: 1
174+
},
175+
buttonGroup: {
176+
flexGrow: 1
177+
},
178+
slider: {
179+
flexGrow: 1
168180
},
169181
button: {
170182
backgroundColor: '#dbdcdb',

example/android/app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ android {
126126
}
127127

128128
dependencies {
129+
compile project(':react-native-fast-image')
129130
compile fileTree(dir: "libs", include: ["*.jar"])
130131
compile "com.android.support:appcompat-v7:23.0.1"
131132
compile "com.facebook.react:react-native:+" // From node_modules

example/android/app/src/main/java/com/example/MainApplication.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.app.Application;
44

55
import com.facebook.react.ReactApplication;
6+
import com.dylanvann.fastimage.FastImageViewPackage;
67
import com.facebook.react.ReactNativeHost;
78
import com.facebook.react.ReactPackage;
89
import com.facebook.react.shell.MainReactPackage;
@@ -22,7 +23,8 @@ public boolean getUseDeveloperSupport() {
2223
@Override
2324
protected List<ReactPackage> getPackages() {
2425
return Arrays.<ReactPackage>asList(
25-
new MainReactPackage()
26+
new MainReactPackage(),
27+
new FastImageViewPackage()
2628
);
2729
}
2830
};

0 commit comments

Comments
 (0)