react-native/Libraries/Components/Touchable/TouchableBounce.js

188 lines
5.9 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
2015-03-25 19:55:10 +00:00
* @flow
* @format
*/
'use strict';
const Animated = require('Animated');
const EdgeInsetsPropType = require('EdgeInsetsPropType');
const NativeMethodsMixin = require('NativeMethodsMixin');
const React = require('React');
const createReactClass = require('create-react-class');
const PropTypes = require('prop-types');
const Touchable = require('Touchable');
const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
const ViewPropTypes = require('ViewPropTypes');
import type {EdgeInsetsProp} from 'EdgeInsetsPropType';
import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback';
import type {ViewStyleProp} from 'StyleSheet';
type Event = Object;
2015-03-25 19:55:10 +00:00
type State = {
animationID: ?number,
scale: Animated.Value,
2015-03-25 19:55:10 +00:00
};
const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
type Props = $ReadOnly<{|
...TouchableWithoutFeedbackProps,
onPressWithCompletion?: ?Function,
onPressAnimationComplete?: ?Function,
pressRetentionOffset?: ?EdgeInsetsProp,
releaseVelocity?: ?number,
releaseBounciness?: ?number,
style?: ?ViewStyleProp,
|}>;
/**
* Example of using the `TouchableMixin` to play well with other responder
* locking views including `ScrollView`. `TouchableMixin` provides touchable
* hooks (`this.touchableHandle*`) that we forward events to. In turn,
* `TouchableMixin` expects us to implement some abstract methods to handle
* interesting interactions such as `handleTouchablePress`.
*/
const TouchableBounce = ((createReactClass({
displayName: 'TouchableBounce',
mixins: [Touchable.Mixin, NativeMethodsMixin],
propTypes: {
...TouchableWithoutFeedback.propTypes,
// The function passed takes a callback to start the animation which should
// be run after this onPress handler is done. You can use this (for example)
// to update UI before starting the animation.
onPressWithCompletion: PropTypes.func,
// the function passed is called after the animation is complete
onPressAnimationComplete: PropTypes.func,
/**
* When the scroll view is disabled, this defines how far your touch may
* move off of the button, before deactivating the button. Once deactivated,
* try moving it back and you'll see that the button is once again
* reactivated! Move it back and forth several times while the scroll view
* is disabled. Ensure you pass in a constant to reduce memory allocations.
*/
pressRetentionOffset: EdgeInsetsPropType,
releaseVelocity: PropTypes.number.isRequired,
releaseBounciness: PropTypes.number.isRequired,
/**
* Style to apply to the container/underlay. Most commonly used to make sure
* rounded corners match the wrapped component.
*/
style: ViewPropTypes.style,
},
getDefaultProps: function() {
return {releaseBounciness: 10, releaseVelocity: 10};
},
2015-03-25 19:55:10 +00:00
getInitialState: function(): State {
return {
...this.touchableGetInitialState(),
scale: new Animated.Value(1),
};
},
2015-03-25 19:55:10 +00:00
bounceTo: function(
value: number,
velocity: number,
bounciness: number,
callback?: ?Function,
2015-03-25 19:55:10 +00:00
) {
Animated.spring(this.state.scale, {
toValue: value,
velocity,
bounciness,
useNativeDriver: true,
}).start(callback);
},
/**
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandleActivePressIn: function(e: Event) {
this.bounceTo(0.93, 0.1, 0);
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
this.bounceTo(1, 0.4, 0);
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
const onPressWithCompletion = this.props.onPressWithCompletion;
2015-03-25 19:55:10 +00:00
if (onPressWithCompletion) {
onPressWithCompletion(() => {
this.state.scale.setValue(0.93);
this.bounceTo(
1,
this.props.releaseVelocity,
this.props.releaseBounciness,
this.props.onPressAnimationComplete,
);
});
return;
}
this.bounceTo(
1,
this.props.releaseVelocity,
this.props.releaseBounciness,
this.props.onPressAnimationComplete,
);
this.props.onPress && this.props.onPress(e);
},
touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
},
touchableGetHitSlop: function(): ?Object {
return this.props.hitSlop;
},
2015-03-25 19:55:10 +00:00
touchableGetHighlightDelayMS: function(): number {
return 0;
},
render: function(): React.Element<any> {
return (
<Animated.View
style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
accessibilityTraits + accessibilityComponentType >> accessibilityRole + accessibilityStates 2/3 Summary: Previously, I created two props, `accessibilityRole` and `accessibilityStates` for view. These props were intended to be a cross-platform solution to replace `accessibilityComponentType` on Android and `accessibilityTraits` on iOS. In this stack, I ran a code mod to replace instances of the two old properties used in our codebase with the new ones. For this diff, I did a search for all the remnant uses of `accessibilityComponentType` that was not caught by my script, and I manually changed them to `accessibilityRole` and `accessibilityStates`. If the same prop also set `accessibilityTraits` I also removed that here because the two new props works on both platforms. It was difficult to write a script for this, because most of them were contextual changes. Out of the contextual changes, most of them followed one of these two patterns: Before: ``` const accessibilityComponentType = 'button'; const accessibilityTraits = ['button']; if (this.props.checked) { accessibilityTraits.push('selected'); } if (this.props.disabled) { accessibilityTraits.push('disabled'); } contentView = ( <AdsManagerTouchableHighlight accessibilityComponentType={accessibilityComponentType} accessibilityTraits={accessibilityTraits} ``` After: const accessibilityRole = 'button'; const accessibilityStates = []; if (this.props.checked) { accessibilityStates.push('selected'); } if (this.props.disabled) { accessibilityStates.push('disabled'); } contentView = ( <AdsManagerTouchableHighlight accessibilityRole={accessibilityRole} accessibilityStates={accessibilityStates} Before: ``` <PressableBackground accessible={this.props.accessible} accessibilityLabel={this.props.accessibilityLabel} accessibilityTraits={this.props.accessibilityTraits} ``` After: ``` <PressableBackground accessible={this.props.accessible} accessibilityLabel={this.props.accessibilityLabel} accessibilityRole={this.props.accessibilityRole} accessibilityRole={this.props.accessibilityStates} ``` In addition to changing the props on the components, Another fix I had to do was to add props accessibilityRole and accessibilityStates to components that don't directly inherit properties from view including text input and touchables. Reviewed By: PeteTheHeat Differential Revision: D8943499 fbshipit-source-id: fbb40a5e5f5d630b0fe56a009ff24635d4c8cc93
2018-07-26 06:37:16 +00:00
accessibilityRole={this.props.accessibilityRole}
accessibilityStates={this.props.accessibilityStates}
nativeID={this.props.nativeID}
testID={this.props.testID}
hitSlop={this.props.hitSlop}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={
this.touchableHandleResponderTerminationRequest
}
onResponderGrant={this.touchableHandleResponderGrant}
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}>
{this.props.children}
{Touchable.renderDebugView({
color: 'orange',
hitSlop: this.props.hitSlop,
})}
</Animated.View>
);
},
}): any): React.ComponentType<Props>);
module.exports = TouchableBounce;