No Description

LoadingSpinnerOverlay.js 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. } from 'react'
  10. import {
  11. View,
  12. Modal,
  13. StyleSheet,
  14. Animated,
  15. Easing,
  16. Dimensions,
  17. ActivityIndicator,
  18. ActivityIndicatorIOS,
  19. ProgressBarAndroid,
  20. ViewPropTypes
  21. } from 'react-native'
  22. import PropTypes from 'prop-types';
  23. import TimerEnhance from 'react-native-smart-timer-enhance'
  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: 20.5,
  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: ViewPropTypes.style,
  51. style: ViewPropTypes.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. let {width: deviceWidth, height: deviceHeight,} = Dimensions.get('window')
  78. return (
  79. this.state.modal ?
  80. (this.state.marginTop === 0 ?
  81. <Modal animationType={'none'} transparent={true} visible={this.state.visible} onRequestClose={noop}>
  82. {loadingSpinner}
  83. </Modal> :
  84. (this.state.visible ?
  85. <View
  86. style={[ styles.overlay, {marginTop: this.props.marginTop, width: deviceWidth, height: deviceHeight - this.props.marginTop,}, this.props.overlayStyle, ]}>
  87. {loadingSpinner}
  88. </View> : null))
  89. : loadingSpinner
  90. )
  91. }
  92. _renderLoadingSpinner() {
  93. let children
  94. if(this.state.children == null) {
  95. children = this._renderActivityIndicator()
  96. }
  97. else {
  98. children = React.Children.map(this.state.children, (child) => {
  99. if (!React.isValidElement(child)) {
  100. return null
  101. }
  102. return child
  103. })
  104. }
  105. return (
  106. this.state.visible ?
  107. <Animated.View
  108. ref={ component => this._container = component }
  109. onLayout={this._onLoadingSpinnerLayout}
  110. style={[styles.container, this.props.style, {opacity:this.state.opacity, }]}>
  111. {children}
  112. </Animated.View> : null
  113. )
  114. }
  115. 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,}
  116. = {modal: this.state.modal, marginTop: this.state.marginTop, children: this.state.children, duration: this.props.duration, easing: Easing.linear, delay: this.props.delay,}) {
  117. this._loadingSpinnerShowAnimation && this._loadingSpinnerShowAnimation.stop()
  118. this._loadingSpinnerHideAnimation && this._loadingSpinnerHideAnimation.stop()
  119. this._loadingSpinnerAnimationToggle && this.clearTimeout(this._loadingSpinnerAnimationToggle)
  120. if(this.state.visible) {
  121. this._setLoadingSpinnerOverlayPosition({modal, marginTop})
  122. }
  123. this.setState({
  124. children,
  125. modal,
  126. marginTop,
  127. visible: true,
  128. })
  129. this._loadingSpinnerShowAnimation = Animated.timing(
  130. this.state.opacity,
  131. {
  132. toValue: 1,
  133. duration,
  134. easing,
  135. delay,
  136. }
  137. )
  138. this._loadingSpinnerShowAnimation.start(() => {
  139. this._loadingSpinnerShowAnimation = null
  140. animationEnd && animationEnd()
  141. })
  142. }
  143. hide({duration = this.props.duration, easing = Easing.linear, delay = this.props.delay, animationEnd,}
  144. = {duration: this.props.duration, easing: Easing.linear, delay: this.props.delay,}) {
  145. this._loadingSpinnerShowAnimation && this._loadingSpinnerShowAnimation.stop()
  146. this._loadingSpinnerHideAnimation && this._loadingSpinnerHideAnimation.stop()
  147. this.clearTimeout(this._loadingSpinnerAnimationToggle)
  148. this._loadingSpinnerHideAnimation = Animated.timing(
  149. this.state.opacity,
  150. {
  151. toValue: 0,
  152. duration,
  153. easing,
  154. delay,
  155. }
  156. )
  157. this._loadingSpinnerHideAnimation.start( () => {
  158. this._loadingSpinnerHideAnimation = null
  159. this.setState({
  160. visible: false,
  161. })
  162. animationEnd && animationEnd()
  163. })
  164. }
  165. _onLoadingSpinnerLayout = (e) => {
  166. this._loadingSpinnerWidth = e.nativeEvent.layout.width
  167. this._loadingSpinnerHeight = e.nativeEvent.layout.height
  168. this._setLoadingSpinnerOverlayPosition()
  169. }
  170. _setLoadingSpinnerOverlayPosition({modal, marginTop} = {modal: this.state.modal, marginTop: this.state.marginTop}) {
  171. if(!this._loadingSpinnerWidth || !this._loadingSpinnerHeight) {
  172. return
  173. }
  174. let {width: deviceWidth, height: deviceHeight,} = Dimensions.get('window')
  175. let left = (deviceWidth - this._loadingSpinnerWidth) / 2
  176. let top = (deviceHeight - this._loadingSpinnerHeight) / 2 - (modal && marginTop === 0 ? 0 : marginTop)
  177. this._container.setNativeProps({
  178. style: {
  179. left,
  180. top,
  181. }
  182. })
  183. }
  184. _renderActivityIndicator() {
  185. return ActivityIndicator ? (
  186. <ActivityIndicator
  187. style={{position: 'relative', left: 1, top: 1,}}
  188. animating={true}
  189. color={'#fff'}
  190. size={'large'}/>
  191. ) : Platform.OS == 'android' ?
  192. (
  193. <ProgressBarAndroid
  194. color={'#fff'}
  195. styleAttr={'large'}/>
  196. ) : (
  197. <ActivityIndicatorIOS
  198. animating={true}
  199. color={'#fff'}
  200. size={'large'}/>
  201. )
  202. }
  203. }
  204. export default TimerEnhance(LoadingSpinnerOverlay)