mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +08:00
[add] initial ScrollView
Supports the following props: `children`, `contentContainerStyle`, `horizontal`, `onScroll`, `scrollEnabled`, `scrollEventThrottle`, and `style`. Fix #6
This commit is contained in:
committed by
Nicolas Gallagher
parent
a1664927ce
commit
894fd0362d
@@ -1,6 +1,7 @@
|
||||
# ScrollView
|
||||
|
||||
TODO
|
||||
Scrollable `View` for use with bounded height, either by setting the height of
|
||||
the view directly (discouraged) or by bounding the height of ancestor views.
|
||||
|
||||
## Props
|
||||
|
||||
@@ -11,27 +12,29 @@ Child content.
|
||||
**contentContainerStyle**: style
|
||||
|
||||
These styles will be applied to the scroll view content container which wraps
|
||||
all of the child views. Example:
|
||||
all of the child views.
|
||||
|
||||
**horizontal**: bool = false
|
||||
|
||||
When true, the scroll view's children are arranged horizontally in a row instead of vertically in a column. Default: `false`.
|
||||
When true, the scroll view's children are arranged horizontally in a row
|
||||
instead of vertically in a column.
|
||||
|
||||
**onScroll**: function
|
||||
|
||||
Fires at most once per frame during scrolling. The frequency of the events can be contolled using the `scrollEventThrottle` prop.
|
||||
Fires at most once per frame during scrolling. The frequency of the events can
|
||||
be contolled using the `scrollEventThrottle` prop.
|
||||
|
||||
**scrollEnabled**: bool
|
||||
**scrollEnabled**: bool = true
|
||||
|
||||
When false, the content does not scroll. Default: `true`.
|
||||
When false, the content does not scroll.
|
||||
|
||||
**scrollEventThrottle**: number
|
||||
**scrollEventThrottle**: number = 0
|
||||
|
||||
This controls how often the scroll event will be fired while scrolling (in
|
||||
events per seconds). A higher number yields better accuracy for code that is
|
||||
tracking the scroll position, but can lead to scroll performance problems.
|
||||
Default: `0` (the scroll event will be sent only once each time the view is
|
||||
scrolled.)
|
||||
tracking the scroll position, but can lead to scroll performance problems. The
|
||||
default value is `0`, which means the scroll event will be sent only once each
|
||||
time the view is scrolled.
|
||||
|
||||
**style**: style
|
||||
|
||||
@@ -40,20 +43,42 @@ scrolled.)
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { ScrollView } from 'react-native-web'
|
||||
import React, { ScrollView, StyleSheet } from 'react-native-web'
|
||||
|
||||
const { Component, PropTypes } = React;
|
||||
import Item from './Item'
|
||||
|
||||
class Example extends Component {
|
||||
static propTypes = {
|
||||
export default class App extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
items: Array.from({ length: 20 }).map((_, i) => ({ id: i }))
|
||||
}
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onScroll(e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView
|
||||
children={this.state.items.map((item) => <Item {...item} />)}
|
||||
contentContainerStyle={styles.container}
|
||||
horizontal
|
||||
onScroll={(e) => this.onScroll(e)}
|
||||
scrollEventThrottle={60}
|
||||
style={styles.root}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
container: {
|
||||
padding: '10px'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import GridView from './GridView'
|
||||
import Heading from './Heading'
|
||||
import MediaQueryWidget from './MediaQueryWidget'
|
||||
import React, { Image, StyleSheet, Text, TextInput, Touchable, View } from '../../src'
|
||||
import React, { Image, StyleSheet, ScrollView, Text, TextInput, Touchable, View } from '../../src'
|
||||
|
||||
export default class App extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -9,6 +9,13 @@ export default class App extends React.Component {
|
||||
style: View.propTypes.style
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this.state = {
|
||||
scrollEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { mediaQuery } = this.props
|
||||
const rootStyles = {
|
||||
@@ -152,6 +159,52 @@ export default class App extends React.Component {
|
||||
)
|
||||
})}
|
||||
</GridView>
|
||||
|
||||
<Heading size='large'>ScrollView</Heading>
|
||||
<label>
|
||||
<input
|
||||
checked={this.state.scrollEnabled}
|
||||
onChange={() => this.setState({
|
||||
scrollEnabled: !this.state.scrollEnabled
|
||||
})}
|
||||
type='checkbox'
|
||||
/> Enable scroll
|
||||
</label>
|
||||
|
||||
<Heading>Default layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<Heading>Horizontal layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={{...styles.box, ...styles.horizontalBox}}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -179,6 +232,9 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
borderWidth: '1px'
|
||||
},
|
||||
horizontalBox: {
|
||||
width: '50px'
|
||||
},
|
||||
boxFull: {
|
||||
width: '100%'
|
||||
},
|
||||
@@ -194,5 +250,14 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1,
|
||||
height: '200px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px'
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
padding: '10px'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"inline-style-prefixer": "^0.3.3",
|
||||
"lodash.debounce": "^3.1.1",
|
||||
"react-tappable": "^0.7.1",
|
||||
"react-textarea-autosize": "^3.0.0"
|
||||
},
|
||||
|
||||
4
src/components/ScrollView/ScrollViewStylePropTypes.js
Normal file
4
src/components/ScrollView/ScrollViewStylePropTypes.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import View from '../View'
|
||||
export default {
|
||||
...(View.stylePropTypes)
|
||||
}
|
||||
@@ -1 +1,11 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
|
||||
import ScrollView from '../'
|
||||
|
||||
suite('components/ScrollView', () => {
|
||||
test('prop "style"', () => {
|
||||
utils.assertProps.style(ScrollView)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,138 @@
|
||||
import { pickProps } from '../../modules/filterObjectProps'
|
||||
import debounce from 'lodash.debounce'
|
||||
import React, { PropTypes } from 'react'
|
||||
import ScrollViewStylePropTypes from './ScrollViewStylePropTypes'
|
||||
import StyleSheet from '../../modules/StyleSheet'
|
||||
import View from '../View'
|
||||
|
||||
const scrollViewStyleKeys = Object.keys(ScrollViewStylePropTypes)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
overflow: 'scroll'
|
||||
},
|
||||
initialContentContainer: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
})
|
||||
|
||||
class ScrollView extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.any
|
||||
children: PropTypes.any,
|
||||
contentContainerStyle: PropTypes.shape(ScrollViewStylePropTypes),
|
||||
horizontal: PropTypes.bool,
|
||||
onScroll: PropTypes.func,
|
||||
scrollEnabled: PropTypes.bool,
|
||||
scrollEventThrottle: PropTypes.number,
|
||||
style: PropTypes.shape(ScrollViewStylePropTypes)
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
className: ''
|
||||
contentContainerStyle: styles.initialContentContainer,
|
||||
horizontal: false,
|
||||
scrollEnabled: true,
|
||||
scrollEventThrottle: 0,
|
||||
style: styles.initial
|
||||
}
|
||||
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this._debouncedOnScrollEnd = debounce(this._onScrollEnd, 100)
|
||||
this.state = {
|
||||
isScrolling: false
|
||||
}
|
||||
}
|
||||
|
||||
_onScroll(e) {
|
||||
const { scrollEventThrottle } = this.props
|
||||
const { isScrolling, scrollLastTick } = this.state
|
||||
|
||||
// A scroll happened, so the scroll bumps the debounce.
|
||||
this._debouncedOnScrollEnd(e)
|
||||
|
||||
if (isScrolling) {
|
||||
// Scroll last tick may have changed, check if we need to notify
|
||||
if (this._shouldEmitScrollEvent(scrollLastTick, scrollEventThrottle)) {
|
||||
this._onScrollTick(e)
|
||||
}
|
||||
} else {
|
||||
// Weren't scrolling, so we must have just started
|
||||
this._onScrollStart(e)
|
||||
}
|
||||
}
|
||||
|
||||
_onScrollStart() {
|
||||
this.setState({
|
||||
isScrolling: true,
|
||||
scrollLastTick: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
_onScrollTick(e) {
|
||||
const { onScroll } = this.props
|
||||
this.setState({
|
||||
scrollLastTick: Date.now()
|
||||
})
|
||||
if (onScroll) onScroll(e)
|
||||
}
|
||||
|
||||
_onScrollEnd(e) {
|
||||
const { onScroll } = this.props
|
||||
this.setState({
|
||||
isScrolling: false
|
||||
})
|
||||
if (onScroll) onScroll(e)
|
||||
}
|
||||
|
||||
_shouldEmitScrollEvent(lastTick, eventThrottle) {
|
||||
const timeSinceLastTick = Date.now() - lastTick
|
||||
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
|
||||
}
|
||||
|
||||
_maybePreventScroll(e) {
|
||||
const { scrollEnabled } = this.props
|
||||
if (!scrollEnabled) e.preventDefault()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
contentContainerStyle,
|
||||
horizontal,
|
||||
style
|
||||
} = this.props
|
||||
|
||||
const resolvedStyle = pickProps(style, scrollViewStyleKeys)
|
||||
const resolvedContentContainerStyle = pickProps(contentContainerStyle, scrollViewStyleKeys)
|
||||
|
||||
return (
|
||||
<View {...this.props} />
|
||||
<View
|
||||
_className='ScrollView'
|
||||
onScroll={(e) => this._onScroll(e)}
|
||||
onTouchMove={(e) => this._maybePreventScroll(e)}
|
||||
onWheel={(e) => this._maybePreventScroll(e)}
|
||||
style={{
|
||||
...styles.initial,
|
||||
...resolvedStyle
|
||||
}}
|
||||
>
|
||||
{children ? (
|
||||
<View
|
||||
children={children}
|
||||
style={{
|
||||
...styles.initialContentContainer,
|
||||
...resolvedContentContainerStyle,
|
||||
...(horizontal && styles.row)
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user