No Description

LoadingSpinnerOverlay.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /*
  2. * A smart loading spinner overlay for React Native apps, written in JS
  3. * https://github.com/react-native-component/react-native-smart-loading-spinner-overlay/
  4. * Released under the MIT license
  5. * Copyright (c) 2016 react-native-component <moonsunfall@aliyun.com>
  6. */
  7. import React, {
  8. Component,
  9. PropTypes,
  10. } from 'react'
  11. import {
  12. View,
  13. Modal,
  14. StyleSheet,
  15. Animated,
  16. Easing,
  17. Dimensions,
  18. ActivityIndicator,
  19. ActivityIndicatorIOS,
  20. ProgressBarAndroid,
  21. } from 'react-native'
  22. import TimerEnhance from 'react-native-smart-timer-enhance'
  23. const {width: deviceWidth, height: deviceHeight,} = Dimensions.get('window')
  24. const styles = StyleSheet.create({
  25. overlay: {
  26. position: 'absolute',
  27. left: 0,
  28. top: 0,
  29. zIndex: 998,
  30. },
  31. container: {
  32. backgroundColor: 'rgba(0, 0, 0, 0.8)',
  33. position: 'absolute',
  34. borderRadius: 8,
  35. padding: 18,
  36. left: -9999,
  37. top: -9999,
  38. zIndex: 999,
  39. },
  40. })
  41. const noop = () => {}
  42. class LoadingSpinnerOverlay extends Component {
  43. static defaultProps = {
  44. duration: 255,
  45. delay: 0,
  46. marginTop: 0,
  47. modal: true,
  48. }
  49. static propTypes = {
  50. overlayStyle: View.propTypes.style,
  51. style: View.propTypes.style,
  52. duration: PropTypes.number,
  53. delay: PropTypes.number,
  54. marginTop: PropTypes.number,
  55. modal: PropTypes.bool,
  56. }
  57. constructor(props) {
  58. super(props)
  59. this.state = {
  60. visible: false,
  61. opacity: new Animated.Value(0),
  62. children: props.children,
  63. modal: props.modal,
  64. marginTop: props.marginTop,
  65. }
  66. this._loadingSpinnerWidth = null
  67. this._loadingSpinnerHeight = null
  68. this._loadingSpinnerShowAnimation = null
  69. this._loadingSpinnerHideAnimation = null
  70. this._loadingSpinnerAnimationToggle = null
  71. }
  72. render() {
  73. let loadingSpinner = this._renderLoadingSpinner()
  74. return this._renderOverLay(loadingSpinner)
  75. }
  76. _renderOverLay(loadingSpinner) {
  77. return (
  78. this.state.modal ?
  79. (this.state.marginTop === 0 ?
  80. <Modal animationType={'none'} transparent={true} visible={this.state.visible} onRequestClose={noop}>
  81. {loadingSpinner}
  82. </Modal> :
  83. (this.state.visible ?
  84. <View
  85. style={[ styles.overlay, {marginTop: this.props.marginTop, width: deviceWidth, height: deviceHeight - this.props.marginTop,}, this.props.overlayStyle, ]}>
  86. {loadingSpinner}
  87. </View> : null))
  88. : loadingSpinner
  89. )
  90. }
  91. _renderLoadingSpinner() {
  92. let children
  93. if(this.state.children == null) {
  94. children = this._renderActivityIndicator()
  95. }
  96. else {
  97. children = React.Children.map(this.state.children, (child) => {
  98. if (!React.isValidElement(child)) {
  99. return null
  100. }
  101. return child
  102. })
  103. }
  104. return (
  105. this.state.visible ?
  106. <Animated.View
  107. ref={ component => this._container = component }
  108. onLayout={this._onLoadingSpinnerLayout}
  109. style={[styles.container, this.props.style, {opacity:this.state.opacity, }]}>
  110. {children}
  111. </Animated.View> : null
  112. )
  113. }
  114. show({modal = this.state.modal, marginTop = this.state.marginTop, children = this.state.children, duration = this.props.duration, easing = Easing.linear, delay = this.props.delay, animationEnd,}
  115. = {modal: this.state.modal, marginTop: this.state.marginTop, children: this.state.children, duration: this.props.duration, easing: Easing.linear, delay: this.props.delay,}) {
  116. this._loadingSpinnerShowAnimation && this._loadingSpinnerShowAnimation.stop()
  117. this._loadingSpinnerHideAnimation && this._loadingSpinnerHideAnimation.stop()
  118. this._loadingSpinnerAnimationToggle && this.clearTimeout(this._loadingSpinnerAnimationToggle)
  119. if(this.state.visible) {
  120. this._setLoadingSpinnerOverlayPosition({modal, marginTop})
  121. }
  122. this.setState({
  123. children,
  124. modal,
  125. marginTop,
  126. visible: true,
  127. })
  128. this._loadingSpinnerShowAnimation = Animated.timing(
  129. this.state.opacity,
  130. {
  131. toValue: 1,
  132. duration,
  133. easing,
  134. delay,
  135. }
  136. )
  137. this._loadingSpinnerShowAnimation.start(() => {
  138. this._loadingSpinnerShowAnimation = null
  139. animationEnd && animationEnd()
  140. })
  141. }
  142. hide({duration = this.props.duration, easing = Easing.linear, delay = this.props.delay, animationEnd,}
  143. = {duration: this.props.duration, easing: Easing.linear, delay: this.props.delay,}) {
  144. this._loadingSpinnerShowAnimation && this._loadingSpinnerShowAnimation.stop()
  145. this._loadingSpinnerHideAnimation && this._loadingSpinnerHideAnimation.stop()
  146. this.clearTimeout(this._loadingSpinnerAnimationToggle)
  147. this._loadingSpinnerHideAnimation = Animated.timing(
  148. this.state.opacity,
  149. {
  150. toValue: 0,
  151. duration,
  152. easing,
  153. delay,
  154. }
  155. )
  156. this._loadingSpinnerHideAnimation.start( () => {
  157. this._loadingSpinnerHideAnimation = null
  158. this.setState({
  159. visible: false,
  160. })
  161. animationEnd && animationEnd()
  162. })
  163. }
  164. _onLoadingSpinnerLayout = (e) => {
  165. this._loadingSpinnerWidth = e.nativeEvent.layout.width
  166. this._loadingSpinnerHeight = e.nativeEvent.layout.height
  167. this._setLoadingSpinnerOverlayPosition()
  168. }
  169. _setLoadingSpinnerOverlayPosition({modal, marginTop} = {modal: this.state.modal, marginTop: this.state.marginTop}) {
  170. if(!this._loadingSpinnerWidth || !this._loadingSpinnerHeight) {
  171. return
  172. }
  173. let left = (deviceWidth - this._loadingSpinnerWidth) / 2
  174. let top = (deviceHeight - this._loadingSpinnerHeight) / 2 - (modal && marginTop === 0 ? 0 : marginTop)
  175. this._container.setNativeProps({
  176. style: {
  177. left,
  178. top,
  179. }
  180. })
  181. }
  182. _renderActivityIndicator() {
  183. return ActivityIndicator ? (
  184. <ActivityIndicator
  185. style={{position: 'relative', left: 1, top: 1,}}
  186. animating={true}
  187. color={'#fff'}
  188. size={'large'}/>
  189. ) : Platform.OS == 'android' ?
  190. (
  191. <ProgressBarAndroid
  192. color={'#fff'}
  193. styleAttr={'large'}/>
  194. ) : (
  195. <ActivityIndicatorIOS
  196. animating={true}
  197. color={'#fff'}
  198. size={'large'}/>
  199. )
  200. }
  201. }
  202. export default TimerEnhance(LoadingSpinnerOverlay)