TouchableBounce.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @flow strict-local
  8. * @format
  9. */
  10. 'use strict';
  11. import Pressability, {
  12. type PressabilityConfig,
  13. } from '../../Pressability/Pressability';
  14. import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
  15. import type {ViewStyleProp} from '../../StyleSheet/StyleSheet';
  16. import TVTouchable from './TVTouchable';
  17. import typeof TouchableWithoutFeedback from './TouchableWithoutFeedback';
  18. import {Animated, Platform} from 'react-native';
  19. import * as React from 'react';
  20. type Props = $ReadOnly<{|
  21. ...React.ElementConfig<TouchableWithoutFeedback>,
  22. onPressAnimationComplete?: ?() => void,
  23. onPressWithCompletion?: ?(callback: () => void) => void,
  24. releaseBounciness?: ?number,
  25. releaseVelocity?: ?number,
  26. style?: ?ViewStyleProp,
  27. hostRef: React.Ref<typeof Animated.View>,
  28. |}>;
  29. type State = $ReadOnly<{|
  30. pressability: Pressability,
  31. scale: Animated.Value,
  32. |}>;
  33. class TouchableBounce extends React.Component<Props, State> {
  34. _tvTouchable: ?TVTouchable;
  35. state: State = {
  36. pressability: new Pressability(this._createPressabilityConfig()),
  37. scale: new Animated.Value(1),
  38. };
  39. _createPressabilityConfig(): PressabilityConfig {
  40. return {
  41. cancelable: !this.props.rejectResponderTermination,
  42. disabled: this.props.disabled,
  43. hitSlop: this.props.hitSlop,
  44. delayLongPress: this.props.delayLongPress,
  45. delayPressIn: this.props.delayPressIn,
  46. delayPressOut: this.props.delayPressOut,
  47. minPressDuration: 0,
  48. pressRectOffset: this.props.pressRetentionOffset,
  49. android_disableSound: this.props.touchSoundDisabled,
  50. onBlur: event => {
  51. if (Platform.isTV) {
  52. this._bounceTo(1, 0.4, 0);
  53. }
  54. if (this.props.onBlur != null) {
  55. this.props.onBlur(event);
  56. }
  57. },
  58. onFocus: event => {
  59. if (Platform.isTV) {
  60. this._bounceTo(0.93, 0.1, 0);
  61. }
  62. if (this.props.onFocus != null) {
  63. this.props.onFocus(event);
  64. }
  65. },
  66. onLongPress: event => {
  67. if (this.props.onLongPress != null) {
  68. this.props.onLongPress(event);
  69. }
  70. },
  71. onPress: event => {
  72. const {onPressAnimationComplete, onPressWithCompletion} = this.props;
  73. const releaseBounciness = this.props.releaseBounciness ?? 10;
  74. const releaseVelocity = this.props.releaseVelocity ?? 10;
  75. if (onPressWithCompletion != null) {
  76. onPressWithCompletion(() => {
  77. this.state.scale.setValue(0.93);
  78. this._bounceTo(
  79. 1,
  80. releaseVelocity,
  81. releaseBounciness,
  82. onPressAnimationComplete,
  83. );
  84. });
  85. return;
  86. }
  87. this._bounceTo(
  88. 1,
  89. releaseVelocity,
  90. releaseBounciness,
  91. onPressAnimationComplete,
  92. );
  93. if (this.props.onPress != null) {
  94. this.props.onPress(event);
  95. }
  96. },
  97. onPressIn: event => {
  98. this._bounceTo(0.93, 0.1, 0);
  99. if (this.props.onPressIn != null) {
  100. this.props.onPressIn(event);
  101. }
  102. },
  103. onPressOut: event => {
  104. this._bounceTo(1, 0.4, 0);
  105. if (this.props.onPressOut != null) {
  106. this.props.onPressOut(event);
  107. }
  108. },
  109. };
  110. }
  111. _bounceTo(
  112. toValue: number,
  113. velocity: number,
  114. bounciness: number,
  115. callback?: ?() => void,
  116. ) {
  117. Animated.spring(this.state.scale, {
  118. toValue,
  119. velocity,
  120. bounciness,
  121. useNativeDriver: true,
  122. }).start(callback);
  123. }
  124. render(): React.Node {
  125. // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
  126. // adopting `Pressability`, so preserve that behavior.
  127. const {
  128. onBlur,
  129. onFocus,
  130. ...eventHandlersWithoutBlurAndFocus
  131. } = this.state.pressability.getEventHandlers();
  132. return (
  133. <Animated.View
  134. style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
  135. accessible={this.props.accessible !== false}
  136. accessibilityLabel={this.props.accessibilityLabel}
  137. accessibilityHint={this.props.accessibilityHint}
  138. accessibilityRole={this.props.accessibilityRole}
  139. accessibilityState={this.props.accessibilityState}
  140. accessibilityActions={this.props.accessibilityActions}
  141. onAccessibilityAction={this.props.onAccessibilityAction}
  142. accessibilityValue={this.props.accessibilityValue}
  143. importantForAccessibility={this.props.importantForAccessibility}
  144. accessibilityLiveRegion={this.props.accessibilityLiveRegion}
  145. accessibilityViewIsModal={this.props.accessibilityViewIsModal}
  146. accessibilityElementsHidden={this.props.accessibilityElementsHidden}
  147. nativeID={this.props.nativeID}
  148. testID={this.props.testID}
  149. hitSlop={this.props.hitSlop}
  150. focusable={
  151. this.props.focusable !== false &&
  152. this.props.onPress !== undefined &&
  153. !this.props.disabled
  154. }
  155. ref={this.props.hostRef}
  156. {...eventHandlersWithoutBlurAndFocus}>
  157. {this.props.children}
  158. {__DEV__ ? (
  159. <PressabilityDebugView color="orange" hitSlop={this.props.hitSlop} />
  160. ) : null}
  161. </Animated.View>
  162. );
  163. }
  164. componentDidMount(): void {
  165. if (Platform.isTV) {
  166. this._tvTouchable = new TVTouchable(this, {
  167. getDisabled: () => this.props.disabled === true,
  168. onBlur: event => {
  169. if (this.props.onBlur != null) {
  170. this.props.onBlur(event);
  171. }
  172. },
  173. onFocus: event => {
  174. if (this.props.onFocus != null) {
  175. this.props.onFocus(event);
  176. }
  177. },
  178. onPress: event => {
  179. if (this.props.onPress != null) {
  180. this.props.onPress(event);
  181. }
  182. },
  183. });
  184. }
  185. }
  186. componentDidUpdate(prevProps: Props, prevState: State) {
  187. this.state.pressability.configure(this._createPressabilityConfig());
  188. }
  189. componentWillUnmount(): void {
  190. if (Platform.isTV) {
  191. if (this._tvTouchable != null) {
  192. this._tvTouchable.destroy();
  193. }
  194. }
  195. this.state.pressability.reset();
  196. }
  197. }
  198. module.exports = (React.forwardRef((props, hostRef) => (
  199. <TouchableBounce {...props} hostRef={hostRef} />
  200. )): React.ComponentType<$ReadOnly<$Diff<Props, {|hostRef: mixed|}>>>);