I'm trying to extend a react-native library for a parallax header which is really nice, by adding a clickable ability to the parallax background.
Attempted a few ways I've found but none seemed to work.
Things tested:
TouchableOpacity within Animated.View with onPress event
TouchableOpacity wrapping the Animated.Image with onPress event
Created an AnimatedTouchable with the onPress event
Nothing is triggering the passed event, animation works, though.
This is the entire component code for the ParallaxHeader that I'm using.
As you may see, I added the TouchableOpacity into this method:
renderHeaderBackground() {
const { backgroundImage, backgroundColor, onBackgroundPress } = this.props;
const currStyles =
[
styles.header,
{
height: this.getHeaderHeight(),
backgroundColor: backgroundColor,
},
];
return (
<Animated.View style={currStyles}>
<TouchableOpacity onPress={onBackgroundPress}>
{backgroundImage && this.renderBackgroundImage()}
{!backgroundImage && this.renderPlainBackground()}
</TouchableOpacity>
</Animated.View>
);
}
As in the other ones too related to Header, but none worked. Any idea? I thought it could be due to the position: 'absolute' property of the header style, but it wasn't, I tested removing it.
Related
Description
When a child of a pressable component is pressed (such as an image), the function passed to the onPress prop does not execute on android. Works as expected on iOS.
React Native version:
0.63.2
Steps To Reproduce
Open a new expo snack
Create a Pressable component that is the parent of some other component (A text or image)
Set the onPress prop to call a function that has a visual effect. (Like an alert)
Switch to the android tab, and click 'Tap to play'
Expected Results
The function is called and the effect (an Alert) is fired
Snack, code example, screenshot, or link to a repository:
https://snack.expo.io/#razorshnegax/6c7be3
Code example:
import React from 'react';
import { View, Pressable, Image, Alert } from 'react-native';
export default class Index extends React.Component {
render() {
// The onPress function fires in iOS, but not android
return (
<View>
<Pressable onPress={() => {Alert.alert("Yeep")}}>
<Image source={require('./greatknight.png')} style={{
// So that the image is more centered
top: 100,
left: 100
}}/>
</Pressable>
</View>
);
}
}
Due to the styling, the Pressable component is not place behind the Image.
You can see this by adding a color to the Pressable component.
A fix for this would be to style the Pressable component so the image components are aligned and events bubble through.
I'm not exactly sure why the event doesn't bubble through on Android as it should based on the component hierarchy.
In my case added zindex to resolved this problem. This work even touchable also
<Pressable onPress={() => alert()} style={{zIndex: 0.5, }}>
*****
</Pressable>
I'm trying to add a hit slop to some icons and I want them to persist even if the icon is hidden under another component.
This is already working fine on iOS with the zIndex prop, but neither the zIndex or elevation prop seem to be having any effect on Android. The icon is still clickable, just without the hit slop surrounding it. This is likely not an issue with the hit slop, as it works fine on a screen alone when not inside of a component.
export const RemoveIndicator = ({ icon, onPress }) => (
<View
style={ {
elevation: 2,
zIndex: 99,
} }
>
<TouchableIcon
color={ colors.Red500 }
name={ icon || 'Circle_Remove_Solid' }
onPress={ onPress }
size={ 16 }
/>
</View>
);
The TouchableIcon component is a component that contains a glyph (given the color, name, and size), wrapped in a TouchableOpacity that takes an onPress function. The hit slop is calculated inside the TouchableIcon based on the size prop.
I expect the icon to be clickable in an area around it, but it is only clickable on the visible glyph itself.
I have a render method with these components:
<View style={styles.root}>
{p.parts.map((part, index) =>
<Animated.View
key={`part${index}`}
style={[
this.draggables[index].pan.getLayout()
]}
{...this.draggables[index].panResponder.panHandlers}
>
<SphereView part={part} />
</Animated.View>
)}
</View>
And this style:
const styles = StyleSheet.create({
root: {
flexDirection: 'row'
}
});
Drag and drop is working, but the problem is that when I drag the second element, it is shown above only the first and below all others. I want the component being dragged to be above all others.
I've tried various combinations with zIndex and none worked.
How to do it?
Edit: I've now just achieved with with the "elevation" style in Android. Is it the correct approach (even for iOS)?
It's a shame that most of the PanResponder questions on SO (or at least the ones Ive seen) have no answer. You can use zIndex. Just set the zIndex to 999 or something during the drag, and then on release, set it to the original zIndex+1, so that it STAYS above the other images. If you kept all of the dragged elements at 999, they would no longer overlap after one drag.
Elevation only works on android, but zIndex works on both, and will not create a shadow/3D effect
When a child of a TouchableHighlight has an opacity, its opacity disappears (is set to 1) after the TouchableHighlight is pressed.
Running example here: https://rnplay.org/apps/c0NIjQ
Minimal example:
<TouchableHighlight onPress={() => {}}>
<Text style={{ opacity: 0.5 }}>
Press me!
</Text>
</TouchableHighlight>
Is there a way to mend this, or is it a bug in React Native?
TouchableOpacity works as I would have expected for TouchableHighlight (live code sample), so using TouchableOpacity could be a workaround. Note, however, that TouchableOpacity does not have an underlay which appears when active, so whatever you render underneath is what will "shine through" on press. Thus, it's not a perfect substitute for TouchableHighlight.
I'm not sure whether the behavior of TouchableHighlight is intended, some sort of a tradeoff or actually a bug, but looking at the code you can clearly see how it differs from TouchableOpacity in this regard:
TouchableHighlight always sets the child's opacity to 1 when it goes inactive.
TouchableOpacity sets the child's opacity to childStyles.opacity if it is set, otherwise 1, when it goes inactive.
You could work around it by implementing the onPressOut method of TouchableHighlight, and binding your opacity to a state property.
constructor (props) {
this.state = {opacity: 0.5}
}
render () {
return (
<TouchableHighlight
onPressOut={() => this.setState({opacity: 0.5})}
opacity={this.state.opacity}
>
....
</TouchableHighlight>
);
}
Not ideal I agree.
After perusing the React Native Documentation I couldn't seem to find out how to make a <ScrollView> have a persistent scrollbar that doesn't fade out. How would I achieve that?
iOS
The underlying iOS native component, UIScrollView (technically, RCTEnhancedScrollView), doesn't support keeping the scroll indicators visible. For this reason, the React Native wrapper around it won't either.
There is a hack to get this working with the native component (see this answer for one approach). To accomplish this in React Native, you'd need to implement this hack on the native side, and then either create your own Native Module or fork React Native and modify their ScrollView component.
That said, the iOS Scroll View interface guidelines discourage this, so you may want to leave the indicators' behavior alone.
Android
A few approaches:
set <item name="android:overScrollMode">always</item>,
set android:fadeScrollbars="false" in XML, or
set ScrollView.setScrollbarFadingEnabled(false) in Java (e.g. in your custom native bridge code)
This is similarly discouraged as nonstandard UI unless you have a strong reason for it.
Adding answer since none of the above worked for me.
Android now has the persistentScrollbar props.
iOS does not support this. So I created a JS solution that can be used as follows:
<SBScrollView persistentScrollbar={true}>...</SBScrollView>
Basically, this functional component will use persistentScrollbar when on Android, while add a bar when we are on iOS. It is not smooth for now, but it is functional.
// #flow
import React, {useState} from 'react';
import {Platform, View, ScrollView} from 'react-native';
type Props = {|
persistentScrollbar?: boolean,
children?: React$Node,
|} & View.propTypes;
export default function SBScrollView({
persistentScrollbar = false,
children,
...other
}: Props) {
const [nativeEvent, setNativeEvent] = useState();
if (Platform.OS === 'android' || !persistentScrollbar) {
// Abdroid supports the persistentScrollbar
return (
<ScrollView persistentScrollbar={persistentScrollbar} {...other}>
{children}
</ScrollView>
);
}
const top = nativeEvent
? nativeEvent.contentOffset.y +
(nativeEvent.contentOffset.y / nativeEvent.contentSize.height) *
nativeEvent.layoutMeasurement.height
: 0;
// iOS does not support persistentScrollbar, so
// lets simulate it with a view.
return (
<ScrollView
scrollEventThrottle={5}
showsVerticalScrollIndicator={false}
onScroll={event => setNativeEvent(event.nativeEvent)}
{...other}>
{children}
<View
style={{
position: 'absolute',
top,
right: 4,
height: 200,
width: 4,
borderRadius: 20,
backgroundColor: 'gray',
}}
/>
</ScrollView>
);
}
I hope this can help others.
I was looking for a solution but I didn't find nothing, then I created a solution, I hope can help you with it.
I created a view View with height and width and put it over my scrollview, after that I used the Props of scrollview like onMomentumScrollBegin, onMomentumScrollEnd, onContentSizeChange and onScroll
after that I make a condition with a boolean variable, if this variable is false, the View is visible, if is false the View is hide, How do I active this variable? with the Prop onMomentumScrollBegin that detect when you use the scrollView and the same way to set the variable in false with onMomentumScrollEnd that detects when the scroll ends.
The Prop onContentSizeChange allows me to get the height and width of my scrollview, this values I used to calculate where would be set the scrollbar/scrollIndicator
and finally with the Prop onScroll I get the position.
the example:
<ScrollView
onMomentumScrollBegin={() => {this.setvarScrollState()}}
onMomentumScrollEnd={() => {this.setvarScrollStateRev()}}
scrollEventThrottle={5}
onContentSizeChange={(w, h) => this.state.hScroll = h}
showsVerticalScrollIndicator={false}
onScroll={event => { this.state.wScroll = event.nativeEvent.contentOffset.y }}
style={{ marginVertical: 15, marginHorizontal:15, width: this.state.measurements3.width}}>
{
Mydata.map((value, index) => {
return <TouchableOpacity>
<Text>{ value.MyDataItem }</Text>
</TouchableOpacity>
})
}
the functions:
setvarScrollState() {
this.setState({VarScroll: true});
}
setvarScrollStateRev() {
this.setState({VarScroll: false});
}
and the variable
this.state = {VarScroll: false}
Then my condition is
!this.state.VarScroll ?
<View
style = {{
marginTop: 200*(this.state.wScroll / this.state.hScroll),
marginLeft:338.5,
height: 35,
width: 2,
backgroundColor: 'grey',
position:'absolute'
}}
/>
: null
Why 200? because is the maximum value that my marginTop can set
Check the picture
Final note:
the scrollView have to be inside a View with the another View (scrollbar)
something like this
<View>
{/*---- ScrollBar and conditions----*/}
<View>
<View>
<ScrollView>
</ScrollView>
</View>