-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Svg with TouchableOpacity #645
Comments
Seems to work fine: https://snack.expo.io/By94nj_3z import React, { Component } from 'react';
import { View, StyleSheet, TouchableHighlight, TouchableOpacity } from 'react-native';
import { Constants, Svg } from 'expo';
export default class App extends Component {
state = { toggle: false };
toggle = () => this.setState(({ toggle }) => ({ toggle: !toggle }));
render() {
const { toggle } = this.state;
return (
<View style={styles.container}>
<TouchableHighlight style={styles.button} onPress={this.toggle}>
<Svg height={100} width={100}>
<Svg.Circle
cx={50}
cy={50}
r={45}
strokeWidth={2.5}
stroke="#e74c3c"
fill="#f1c40f"
/>
<Svg.Rect
x={15}
y={15}
width={70}
height={70}
strokeWidth={2}
stroke="#9b59b6"
fill={toggle ? '#3498db' : '#9b59b6'}
/>
</Svg>
</TouchableHighlight>
<TouchableOpacity style={styles.button} onPress={this.toggle}>
<Svg height={100} width={100}>
<Svg.Circle
cx={50}
cy={50}
r={45}
strokeWidth={2.5}
stroke="#e74c3c"
fill="#f1c40f"
/>
<Svg.Rect
x={15}
y={15}
width={70}
height={70}
strokeWidth={2}
stroke="#9b59b6"
fill={toggle ? '#3498db' : '#9b59b6'}
/>
</Svg>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0ff',
},
button: {
alignItems: 'center',
backgroundColor: '#DDDDDD',
padding: 10,
},
}); |
@msand this works but unfortunately it would be more convenient if the svg shapes highlighted when touched. Currently you can press the whole SVG to highlight it. What I want is to highlight only the shapes (is the TouchableOpacity could be "nested" inside the root Svg tag) As far as I know it's not possible |
I am facing the same issue. |
Finaly it was a mistake from my code. I have the correct opacity behaviour on press. Can you share your code to help you on your issue ?a |
@slorber @Naeoth I have an experiment for using TouchableOpacity inside svg content here: https://snack.expo.io/@msand/touchableopacityg no changes needed inside the library. |
With the latest version, it works better than in the version bundled in Expo. Example: import React from "react";
import Svg, {
Rect,
} from "react-native-svg";
import TouchableOpacityG from './TouchableOpacityG';
export default () => (
<Svg width="200" height="200" viewBox="0 0 100 100">
<Rect
x="0"
y="0"
width="100"
height="50"
fill="red"
onPress={e => {
console.log('press1', e);
}}
/>
<TouchableOpacityG
onPress={e => {
console.log('press2', e);
}}>
<Rect x="0" y="50" width="100" height="50" fill="blue" />
</TouchableOpacityG>
</Svg>
); Can use this in plain react-native: import React from 'react';
import {
Animated,
Easing,
Platform,
Touchable,
TouchableWithoutFeedback,
NativeMethodsMixin,
} from 'react-native';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import invariant from 'invariant';
import Svg, {
G,
} from "react-native-svg";
const AnimatedG = Animated.createAnimatedComponent(G);
const ensurePositiveDelayProps = function(props: any) {
invariant(
!(
props.delayPressIn < 0 ||
props.delayPressOut < 0 ||
props.delayLongPress < 0
),
'Touchable components cannot have negative delay properties'
);
};
function flattenStyle(style) {
if (style === null || typeof style !== 'object') {
return undefined;
}
if (!Array.isArray(style)) {
return style;
}
const result = {};
for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
const computedStyle = flattenStyle(style[i]);
if (computedStyle) {
for (const key in computedStyle) {
result[key] = computedStyle[key];
}
}
}
return result;
}
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
/**
* A wrapper for making views respond properly to touches.
* On press down, the opacity of the wrapped view is decreased, dimming it.
*
* Opacity is controlled by wrapping the children in an Animated.G, which is
* added to the view hiearchy. Be aware that this can affect layout.
*
* Example:
*
* ```
* renderButton: function() {
* return (
* <TouchableOpacity onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('./myButton.png')}
* />
* </TouchableOpacity>
* );
* },
* ```
* ### Example
*
* ```ReactNativeWebPlayer
* import React from "react";
* import {
* AppRegistry,
* } from 'react-native'
* import Svg, {
* Rect,
* } from "react-native-svg";
*
* import TouchableOpacityG from './TouchableOpacityG';
*
* const App = () => (
* <Svg width="200" height="200" viewBox="0 0 100 100">
* <Rect
* x="0"
* y="0"
* width="100"
* height="50"
* fill="red"
* onPress={e => {
* console.log('press1', e);
* }}
* />
* <TouchableOpacityG
* onPress={e => {
* console.log('press2', e);
* }}>
* <Rect x="0" y="50" width="100" height="50" fill="blue" />
* </TouchableOpacityG>
* </Svg>
* );
*
* AppRegistry.registerComponent('App', () => App)
* ```
*
*/
const TouchableOpacityG = ((createReactClass({
displayName: 'TouchableOpacityG',
mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur, NativeMethodsMixin],
propTypes: {
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete this
* comment and run Flow. */
...TouchableWithoutFeedback.propTypes,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active. Defaults to 0.2.
*/
activeOpacity: PropTypes.number,
/**
* TV preferred focus (see documentation for the View component).
*/
hasTVPreferredFocus: PropTypes.bool,
/**
* Apple TV parallax effects
*/
tvParallaxProperties: PropTypes.object,
},
getDefaultProps: function() {
return {
activeOpacity: 0.2,
};
},
getInitialState: function() {
return {
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
//...this.touchableGetInitialState(),
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
anim: new Animated.Value(this._getChildStyleOpacityWithDefault()),
};
},
componentDidMount: function() {
ensurePositiveDelayProps(this.props);
},
UNSAFE_componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
},
componentDidUpdate: function(prevProps, prevState) {
if (this.props.disabled !== prevProps.disabled) {
this._opacityInactive(250);
}
},
/**
* Animate the touchable to a new opacity.
*/
setOpacityTo: function(value: number, duration: number) {
console.log('setOpacityTo', value, duration);
Animated.timing(this.state.anim, {
toValue: value,
duration: duration,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}).start();
},
/**
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandleActivePressIn: function(e: PressEvent) {
if (e.dispatchConfig.registrationName === 'onResponderGrant') {
this._opacityActive(0);
} else {
this._opacityActive(150);
}
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: PressEvent) {
this._opacityInactive(250);
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandleFocus: function(e: Event) {
if (Platform.isTV) {
this._opacityActive(150);
}
this.props.onFocus && this.props.onFocus(e);
},
touchableHandleBlur: function(e: Event) {
if (Platform.isTV) {
this._opacityInactive(250);
}
this.props.onBlur && this.props.onBlur(e);
},
touchableHandlePress: function(e: PressEvent) {
this.props.onPress && this.props.onPress(e);
},
touchableHandleLongPress: function(e: PressEvent) {
this.props.onLongPress && this.props.onLongPress(e);
},
touchableGetPressRectOffset: function() {
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
},
touchableGetHitSlop: function() {
return this.props.hitSlop;
},
touchableGetHighlightDelayMS: function() {
return this.props.delayPressIn || 0;
},
touchableGetLongPressDelayMS: function() {
return this.props.delayLongPress === 0
? 0
: this.props.delayLongPress || 500;
},
touchableGetPressOutDelayMS: function() {
return this.props.delayPressOut;
},
_opacityActive: function(duration: number) {
console.log('_opacityActive', duration);
this.setOpacityTo(this.props.activeOpacity, duration);
},
_opacityInactive: function(duration: number) {
console.log('_opacityInactive', duration);
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete this
* comment and run Flow. */
this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration);
},
_getChildStyleOpacityWithDefault: function() {
const childStyle = flattenStyle(this.props.style) || {};
return childStyle.opacity == null ? 1 : childStyle.opacity;
},
render: function() {
return (
<AnimatedG
accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
accessibilityRole={this.props.accessibilityRole}
accessibilityStates={this.props.accessibilityStates}
opacity={this.state.anim}
nativeID={this.props.nativeID}
testID={this.props.testID}
onLayout={this.props.onLayout}
isTVSelectable={true}
hasTVPreferredFocus={this.props.hasTVPreferredFocus}
tvParallaxProperties={this.props.tvParallaxProperties}
hitSlop={this.props.hitSlop}
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses
* an error found when Flow v0.89 was deployed. To see the error,
* delete this comment and run Flow. */
this.touchableHandleResponderTerminationRequest
}
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
onResponderGrant={this.touchableHandleResponderGrant}
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
onResponderMove={this.touchableHandleResponderMove}
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
onResponderRelease={this.touchableHandleResponderRelease}
/* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.89 was deployed. To see the error, delete
* this comment and run Flow. */
onResponderTerminate={this.touchableHandleResponderTerminate}
onPress={this.touchableHandlePress}
onPressIn={this.touchableHandleActivePressIn}
onPressOut={this.touchableHandleActivePressOut}>
{this.props.children}
</AnimatedG>
);
},
}): any): React.ComponentType<Props>);
module.exports = TouchableOpacityG; |
Simplified into plain component: import React, { Component } from 'react';
import { Animated, Easing } from 'react-native';
import { G } from 'react-native-svg';
const AnimatedG = Animated.createAnimatedComponent(G);
function flattenStyle(style) {
if (style === null || typeof style !== 'object') {
return undefined;
}
if (!Array.isArray(style)) {
return style;
}
const result = {};
for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
const computedStyle = flattenStyle(style[i]);
if (computedStyle) {
for (const key in computedStyle) {
result[key] = computedStyle[key];
}
}
}
return result;
}
/**
* A wrapper for making svg elements respond properly to touches.
* On press down, the opacity of the wrapped element is decreased, dimming it.
*
* Opacity is controlled by wrapping the children in an Animated.G, which is
* added to the view hierarchy.
*
* Example:
*
* ```
* renderRect: function() {
* return (
* <TouchableOpacityG
* onPress={e => {
* console.log('press', e);
* }}>
* <Rect x="0" y="50" width="100" height="50" fill="blue" />
* </TouchableOpacityG>
* );
* },
* ```
* ### Example
*
* ```ReactNativeWebPlayer
* import React from "react";
* import {
* AppRegistry,
* } from 'react-native'
* import Svg, {
* Rect,
* } from "react-native-svg";
*
* import TouchableOpacityG from './TouchableOpacityG';
*
* const App = () => (
* <Svg width="200" height="200" viewBox="0 0 100 100">
* <Rect
* x="0"
* y="0"
* width="100"
* height="50"
* fill="red"
* onPress={e => {
* console.log('press1', e);
* }}
* />
* <TouchableOpacityG
* onPress={e => {
* console.log('press2', e);
* }}>
* <Rect x="0" y="50" width="100" height="50" fill="blue" />
* </TouchableOpacityG>
* </Svg>
* );
*
* AppRegistry.registerComponent('App', () => App)
* ```
*
*/
export default class TouchableOpacityG extends Component {
static defaultProps = {
activeOpacity: 0.2,
};
componentDidUpdate = (prevProps, prevState) => {
if (this.props.disabled !== prevProps.disabled) {
this._opacityInactive(250);
}
};
/**
* Animate the touchable to a new opacity.
*/
setOpacityTo = (value, duration) => {
Animated.timing(this.state.anim, {
toValue: value,
duration: duration,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}).start();
};
touchableHandleActivePressIn = e => {
if (e.dispatchConfig.registrationName === 'onResponderGrant') {
this._opacityActive(0);
} else {
this._opacityActive(150);
}
this.props.onPressIn && this.props.onPressIn(e);
};
touchableHandleActivePressOut = e => {
this._opacityInactive(250);
this.props.onPressOut && this.props.onPressOut(e);
};
touchableHandlePress = e => {
this.props.onPress && this.props.onPress(e);
};
touchableHandleLongPress = e => {
this.props.onLongPress && this.props.onLongPress(e);
};
_opacityActive = duration => {
this.setOpacityTo(this.props.activeOpacity, duration);
};
_opacityInactive = duration => {
this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration);
};
_getChildStyleOpacityWithDefault = () => {
const childStyle = flattenStyle(this.props.style) || {};
return childStyle.opacity == null ? 1 : childStyle.opacity;
};
state = {
anim: new Animated.Value(this._getChildStyleOpacityWithDefault()),
};
render() {
return (
<AnimatedG
opacity={this.state.anim}
onPress={this.touchableHandlePress}
onLongPress={this.touchableHandleLongPress}
onPressIn={this.touchableHandleActivePressIn}
onPressOut={this.touchableHandleActivePressOut}
>
{this.props.children}
</AnimatedG>
);
}
} |
thanks @msand this is great to know ;) will keep this in mind the next time i need this |
The solution above doesn't work on all Android devices. e.g. Works on Essential PH-1 and it doesn't on LG G5 or Samsung S21 - the ripple effect of touching works, onPress is not called though. |
#1634 |
Can anyone please tell us how can we do it ? |
Did someone find a solution ? |
Hello, |
Hi,
I would like to create my image buttons with SVG using TouchableOpacity element. But my problem is I don't have the opacity event triggered on press button.
This is my render :
render() { return ( <TouchableOpacity onPress={this.props.onPress}> <Svg width={100} height={100} viewBox="0 0 104 104" > .... </Svg> </TouchableOpacity> ); }
I would like to know how to create a button with SVG image (if it is possible).
The text was updated successfully, but these errors were encountered: