[Animated][BREAKING_CHANGE] Convert <TouchableOpacity> to Animated
Summary: Because we don't want to integrate Animated inside of the core of React, we can only pass Animated.Value to styles of <Animated.View>. TouchableOpacity unfortunately used cloneElement. This means that we should have asked every single call site to replace their children to Animated.View. This isn't great. The other solution is to stop using cloneElement and instead wrap the children inside of an <Animated.View>. This has many advantages: - We no longer use cloneElement so we're no longer messing up with elements that are not our own. - Refs are now working correctly for children elements - No longer need to enforce that there's only one child and that this child is a native element The downside is that we're introducing a <View> into the hierarchy. Sadly with CSS there is no way to have a View that doesn't affect layout. What we need to do is to remove the inner <View> and transfer all the styles to the TouchableOpacity. It is annoying but fortunately a pretty mechanical process. I think that having a wrapper is the best solution. I will investigate to see if we can make wrappers on TouchableHighliht and TouchableWithoutFeedback as well. **Upgrade Path:** If the child is a View, move the style of the View to TouchableOpacity and remove the View itself. ``` <TouchableOpacity onPress={...}> <View style={...}> ... </View> </TouchableOpacity> --> <TouchableOpacity onPress={...} style={...}> ... </TouchableOpacity> ``` If the child is an Image or Text, on all the examples at Facebook it worked without any change. But it is a great idea to double check them anyway.
This commit is contained in:
parent
6213950334
commit
725053acfe
|
@ -48,19 +48,19 @@ var Thumb = React.createClass({
|
|||
},
|
||||
render: function() {
|
||||
return (
|
||||
<TouchableOpacity onPress={this._onPressThumb}>
|
||||
<View style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
{this.state.dir === 'column' ?
|
||||
<Text>
|
||||
Oooo, look at this new text! So awesome it may just be crazy.
|
||||
Let me keep typing here so it wraps at least one line.
|
||||
</Text> :
|
||||
<Text />
|
||||
}
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={this._onPressThumb}
|
||||
style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
{this.state.dir === 'column' ?
|
||||
<Text>
|
||||
Oooo, look at this new text! So awesome it may just be crazy.
|
||||
Let me keep typing here so it wraps at least one line.
|
||||
</Text> :
|
||||
<Text />
|
||||
}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
@ -127,14 +127,12 @@ var ListViewPagingExample = React.createClass({
|
|||
<View><Text style={styles.text}>1 Like</Text></View> :
|
||||
null;
|
||||
return (
|
||||
<TouchableOpacity onPress={this._onPressHeader}>
|
||||
<View style={styles.header}>
|
||||
{headerLikeText}
|
||||
<View>
|
||||
<Text style={styles.text}>
|
||||
Table Header (click me)
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={this._onPressHeader} style={styles.header}>
|
||||
{headerLikeText}
|
||||
<View>
|
||||
<Text style={styles.text}>
|
||||
Table Header (click me)
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
|
@ -55,26 +55,24 @@ var BreadcrumbNavSample = React.createClass({
|
|||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(_getRandomRoute())}>
|
||||
<View>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</View>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
iconForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity onPress={() => {
|
||||
navigator.popToRoute(route);
|
||||
}}>
|
||||
<View style={styles.crumbIconPlaceholder} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => { navigator.popToRoute(route); }}
|
||||
style={styles.crumbIconPlaceholder}
|
||||
/>
|
||||
);
|
||||
},
|
||||
separatorForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity onPress={navigator.pop}>
|
||||
<View style={styles.crumbSeparatorPlaceholder} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={navigator.pop}
|
||||
style={styles.crumbSeparatorPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -51,12 +51,11 @@ var NavigationBarRouteMapper = {
|
|||
var previousRoute = navState.routeStack[index - 1];
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.pop()}>
|
||||
<View style={styles.navBarLeftButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
{previousRoute.title}
|
||||
</Text>
|
||||
</View>
|
||||
onPress={() => navigator.pop()}
|
||||
style={styles.navBarLeftButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
{previousRoute.title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
@ -64,12 +63,11 @@ var NavigationBarRouteMapper = {
|
|||
RightButton: function(route, navigator, index, navState) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(newRandomRoute())}>
|
||||
<View style={styles.navBarRightButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
Next
|
||||
</Text>
|
||||
</View>
|
||||
onPress={() => navigator.push(newRandomRoute())}
|
||||
style={styles.navBarRightButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
Next
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -59,12 +59,12 @@ var IntegrationTestsApp = React.createClass({
|
|||
<View style={styles.separator} />
|
||||
<ScrollView>
|
||||
{TESTS.map((test) => [
|
||||
<TouchableOpacity onPress={() => this.setState({test})}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.testName}>
|
||||
{test.displayName}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => this.setState({test})}
|
||||
style={styles.row}>
|
||||
<Text style={styles.testName}>
|
||||
{test.displayName}
|
||||
</Text>
|
||||
</TouchableOpacity>,
|
||||
<View style={styles.separator} />
|
||||
])}
|
||||
|
|
|
@ -58,19 +58,19 @@ var WebViewExample = React.createClass({
|
|||
return (
|
||||
<View style={[styles.container]}>
|
||||
<View style={[styles.addressBarRow]}>
|
||||
<TouchableOpacity onPress={this.goBack}>
|
||||
<View style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'<'}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={this.goBack}
|
||||
style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'<'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={this.goForward}>
|
||||
<View style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'>'}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={this.goForward}
|
||||
style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'>'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TextInput
|
||||
ref={TEXT_INPUT_REF}
|
||||
|
|
|
@ -12,19 +12,16 @@
|
|||
|
||||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var Animated = require('Animated');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var POPAnimationMixin = require('POPAnimationMixin');
|
||||
var React = require('React');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('Touchable');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
|
||||
var cloneWithProps = require('cloneWithProps');
|
||||
var ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var keyOf = require('keyOf');
|
||||
var onlyChild = require('onlyChild');
|
||||
|
||||
/**
|
||||
* A wrapper for making views respond properly to touches.
|
||||
|
@ -49,7 +46,7 @@ var onlyChild = require('onlyChild');
|
|||
*/
|
||||
|
||||
var TouchableOpacity = React.createClass({
|
||||
mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
|
||||
mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],
|
||||
|
||||
propTypes: {
|
||||
...TouchableWithoutFeedback.propTypes,
|
||||
|
@ -67,16 +64,17 @@ var TouchableOpacity = React.createClass({
|
|||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.touchableGetInitialState();
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
ensurePositiveDelayProps(this.props);
|
||||
ensureComponentIsNative(this.refs[CHILD_REF]);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
ensureComponentIsNative(this.refs[CHILD_REF]);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
|
@ -84,22 +82,10 @@ var TouchableOpacity = React.createClass({
|
|||
},
|
||||
|
||||
setOpacityTo: function(value) {
|
||||
if (POPAnimationMixin) {
|
||||
// Reset with animation if POP is available
|
||||
this.stopAllAnimations();
|
||||
var anim = {
|
||||
type: this.AnimationTypes.linear,
|
||||
property: this.AnimationProperties.opacity,
|
||||
duration: 0.15,
|
||||
toValue: value,
|
||||
};
|
||||
this.startAnimation(CHILD_REF, anim);
|
||||
} else {
|
||||
// Reset immediately if POP is unavailable
|
||||
this.refs[CHILD_REF].setNativeProps({
|
||||
opacity: value
|
||||
});
|
||||
}
|
||||
Animated.timing(
|
||||
this.state.anim,
|
||||
{toValue: value, duration: 150}
|
||||
).start();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -158,25 +144,27 @@ var TouchableOpacity = React.createClass({
|
|||
_opacityInactive: function() {
|
||||
this.clearTimeout(this._hideTimeout);
|
||||
this._hideTimeout = null;
|
||||
var child = onlyChild(this.props.children);
|
||||
var childStyle = flattenStyle(child.props.style) || {};
|
||||
var childStyle = flattenStyle(this.props.style) || {};
|
||||
this.setOpacityTo(
|
||||
childStyle.opacity === undefined ? 1 : childStyle.opacity
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return cloneWithProps(onlyChild(this.props.children), {
|
||||
ref: CHILD_REF,
|
||||
accessible: true,
|
||||
testID: this.props.testID,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
onResponderMove: this.touchableHandleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
});
|
||||
return (
|
||||
<Animated.View
|
||||
accessible={true}
|
||||
style={[this.props.style, {opacity: this.state.anim}]}
|
||||
testID={this.props.testID}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -188,6 +176,5 @@ var TouchableOpacity = React.createClass({
|
|||
*/
|
||||
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
var CHILD_REF = keyOf({childRef: null});
|
||||
|
||||
module.exports = TouchableOpacity;
|
||||
|
|
|
@ -174,15 +174,13 @@ var WarningRow = React.createClass({
|
|||
{...this.panGesture.panHandlers}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onOpened}>
|
||||
<View>
|
||||
<Text
|
||||
style={styles.warningText}
|
||||
numberOfLines={2}
|
||||
ref={text => { this.text = text; }}>
|
||||
{countText}
|
||||
{this.props.warning}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={styles.warningText}
|
||||
numberOfLines={2}
|
||||
ref={text => { this.text = text; }}>
|
||||
{countText}
|
||||
{this.props.warning}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View
|
||||
ref={closeButton => { this.closeButton = closeButton; }}
|
||||
|
@ -212,30 +210,27 @@ var WarningBoxOpened = React.createClass({
|
|||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={this.props.onClose}>
|
||||
<View style={styles.yellowBox}>
|
||||
<Text style={styles.yellowBoxText}>
|
||||
{countText}
|
||||
{this.props.warning}
|
||||
</Text>
|
||||
<View style={styles.yellowBoxButtons}>
|
||||
<View style={styles.yellowBoxButton}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onDismissed}>
|
||||
<Text style={styles.yellowBoxButtonText}>
|
||||
Dismiss
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.yellowBoxButton}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onIgnored}>
|
||||
<Text style={styles.yellowBoxButtonText}>
|
||||
Ignore
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
onPress={this.props.onClose}
|
||||
style={styles.yellowBox}>
|
||||
<Text style={styles.yellowBoxText}>
|
||||
{countText}
|
||||
{this.props.warning}
|
||||
</Text>
|
||||
<View style={styles.yellowBoxButtons}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onDismissed}
|
||||
style={styles.yellowBoxButton}>
|
||||
<Text style={styles.yellowBoxButtonText}>
|
||||
Dismiss
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onIgnored}
|
||||
style={styles.yellowBoxButton}>
|
||||
<Text style={styles.yellowBoxButtonText}>
|
||||
Ignore
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue