I'm developing a React Native App using Redux and I want to do a simple task:
Collapse or close a search bar of another component depending on the Y position of a ScrollView.
We have a ListView:
<ScrollView style={styles.scene} onScroll={(event) => {this.onScroll(event)}}>
and this function:
onScroll(event) {
var currentOffset = event.nativeEvent.contentOffset.y;
var direction = currentOffset > this.offset ? 'down' : 'up';
this.offset = currentOffset;
if(currentOffset == 0 && !this.props.isCerca && this.props.textInputTextShop != ""){
this.props.setCerca(true);
}else{
if(currentOffset >50 && this.props.isCerca) this.props.setCerca(false);
}
}
The main problem is that:
When we reach the 0 on the Y (Top of the ListView) the searchbar will be closed BUT if we keep the finger on the screen this event will be fired a lot of times. When we call the function this.props.setCerca(false); the reducer takes time to modify the boolean this.props.isCerca so the searchbar will be collapsed multiple times causing a strange behavior.
How I can set this boolean immediately using redux to returning all the next times without recalling the reducer? Is there a way to "wait" a reducer action? I have to use a local component variable?
Thank you.
Related
I have a simple horizontal slider built using the ScrollView component. There is a button to go to the next slide and when pressed I call scrollTo method on that ScrollView component. I also need to detect when the scroll is finished to display some other animations. I tried using onMomentumScrollEnd and onScrollEndDrag callbacks but on android neither of them is called when I use scrollTo method. Is there any other way to detect scroll 'end' event?
Here is simple example on snack: https://snack.expo.io/#levani/forlorn-bacon
Try this solution, i've modified your snack
I've used onSroll listener with the function :
const scrollDirection = (event) => {
const offsetX = event.nativeEvent.contentOffset.x
const dif = offsetX - (offset || 0);
console.log('dif',dif)
if (dif == 200) {
alert('destination reached')
}
setOffset(offsetX);
};
We have already developed a swiper using React Native PanResponder, (before somebody comment) - we cannot use any other swiper library here. In the swiper we developed, we are facing an issue when a user ends the swipe(meaning releases the Pan) there is a lag when starting a spring animation.
So to fix that, we are trying to move from React Native Animated API to Reanimated Libary which could solve the problem of lag the user is facing.
But while developing with React Native Gesture Handler (PanGestureHandler) and
Reanimated Library, we're unable to detect swipe direction in the
gesture handler.
I'm adding the part of code which we used to detect swipe direction in PanResponder
onPanMoving = (evt, {dx}) => {
this.draggedEnd = dx < -this.swipeThreshold; //You are moving towards right screen
this.draggedStart = dx > this.swipeThreshold; //You are moving towards left screen
}
As you see, detecting the direction while the pan is moving was so easy in PanResponder.
Here comes the problem with Gesture Handler,
I'm unable to detect swipe while the gesture state is active
I have already searched for issues in Gesture Handler and I found this.
There were two workaround suggested in the issue
First one is by Sonaye which has two handler and detects the
swipe direction in onHandlerStateChange for both the handlers
which was not helpful while using reanimated because it only sets swipe direction
when handlers state changes.
Second one is by Satya which actually detects the swipe when
the State is ACTIVE but he uses translationX property, the
translationX property can also negative for us and can move in
either direction similar to swiper.
Both the workarounds doesn't solve our problem.
So is there any other way to find direction using PanGestureHandler and Reanimated. I tried using PanResponder with Reanimated with dx value but ended up with error message i.e nativeEvent properties are only support as dx is from gestureState param from PanResponder.
Note: We cannot use FlingGestureHandler and Swipeables because we need to find direction in Fling still onHandlerStateChange and Swipeables doesn't use Reanimated.
You can detect direction when swipe horizontal via property velocityX
Example :
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.velocityX > 0) {
console.log('Swipe right');
} else {
console.log('Swipe left');
}
};
return (
<PanGestureHandler onGestureEvent={handleGesture}>
// child component
</PanGestureHandler>
);
For those who want to use double FlingGestureHandler to have vertical or horizontal fling gesture here is the solution:
First you have to initialize the stateHandlers for the direction you want. In my case, i was trying to implement a vertical flingGestureHandler.
const onDownFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(COLLAPSED_HEIGHT, {
duration: ANIMATION_DURATION,
});
}
};
const onUpFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(
DEVICE_HEIGHT - TOP_PADDING_FOR_EXPANDED,
{
duration: ANIMATION_DURATION,
},
);
}
};
and then you have to bind those handlers to 2 FlingGestureHandler component of RNGH
<FlingGestureHandler
onHandlerStateChange={onUpFlingHandlerStateChange}
onEnded={() => {
onPressChecklistIcon();
}}
direction={Directions.UP}>
<FlingGestureHandler
direction={Directions.DOWN}
onEnded={() => {
onPressChecklistIcon();
}}
onHandlerStateChange={onDownFlingHandlerStateChange}>
<Animated.View style={[styles.container, reanimatedStyle]}>
<View style={styles.closerContainer}>
<View style={styles.closer} />
</View>
{renderChecklistContent()}
</Animated.View>
</FlingGestureHandler>
</FlingGestureHandler>
Use translationX property rather than velocityX property by nativeEvent:
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.translationX > 0) {
Alert.alert('Swipe right');
} else {
Alert.alert('Swipe left');
}
};
In my case I have used double FlingGestureHandler to detect the direction. It's worked for me.
So i am on RN 49.3,
I am looking for a way to get the scroll position or the index of the item visible on the virtualizedList!
PS: when i opened the source code of VirtualizedList.js which seems to have props.onScrollEndDrag & props.onScroll
We were using a different approach. It has changed a lot since, but I can help you with the initial approach. We added onScroll param to the list. We captured event.nativeEvent.contentOffset.y into the state or some variable inside spec. We used redux. When the user left the screen we saved this value inside a database. Second part was to load this value from the db in componentDidMount . You just put a ref into the list component and then call this.refs.myRef.scrollTo({ x: 0, y: loadedValue, animated: false });
Capture the scroll
render() {
return (
<VirtualizedList
ref='myRef'
onScroll={event => {
this.scroll = event.nativeEvent.contentOffset.y;
}}
...
/>
);}
Save on exit
componentWillUnmount() {
AsyncStorage.setItem(key, this.scroll);
}
Load after mounting
componentDidMount() {
AsyncStorage.getItem(key)
.then(y => {
this.refs.myRef.scrollTo({ x: 0, y, animated: false });
});
}
Best approach I think is to handle it inside redux and you connect this component with the list to the store. I mentioned saving into db because we do save the position for later use, this is only optional and depends on your requirements.
You may use the state as well, but then you need to handle the unnecessary updates
I'm using onLayout to detect screen orientation and it's working fine inside my root view, but when I implemented inside the drawer it didn't work, any reason why this happens ?
code :
import Drawer from 'react-native-drawer'
...
onLayout(e) {
console.log('onLayout');
}
<Drawer onLayout={this.onLayout}
It didn't log any thing when orientation changed!
This is because the Drawer component doesn't take onLayout as a prop. You can see in the source code that the rendered View does use onLayout, but it's not pulling from something like this.props.onLayout.
I'm not exactly sure what you're looking to do, but maybe this issue will help you. As it shows, you can pass a function into openDrawerOffset instead of an integer or a ratio in order to be a little more dynamic with how you set your offset:
openDrawerOffset={(viewport) => {
if (viewport.width < 400) {
return viewport.width * 0.1;
}
return viewport.width - 400;
}}
You might also benefit from the Event handlers that react-native-drawer has to offer.
Is there any callbacks to rendered components from the Navigator when a view gets on / off screen?
In the renderScene() method, you can pass a prop like isRendered={route.index === 0} for the first card, isRendered={route.index === 1} for the second, etc.
And then in the components themselves, you can use componentWillReceiveProps like:
componentWillReceiveProps(next) {
const { isRendered } = this.props
if (isRendered && !next.isRendered) // going off screen
if (!isRendered && next.isRendered) // coming on screen
}