I want to get the current y-offset of my scrollView by using a PanResponder. this is what i did so far, but i couldn't find a way to get the current y-offset
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
console.log(this.scrollView) //this logs my scrollView
}
});
}
<ScrollView ref={(view) => this._scrollView = view} {...this._panResponder.panHandlers}>
Assuming that by touch release you mean the moment when one touch stops existing, you can do that with ScrollVIew's onTouchEnd prop.
I would do that by using onScroll to save the offset somewhere and onTouchEnd to do something with that offset.
<ScrollView
onScroll={(event) => {
this.offsetY = event.nativeEvent.contentOffset.y;
}}
onTouchEnd={(event) => {
console.log('offsetY:', this.offsetY);
console.log('touch info:', event.nativeEvent);
}}
>
{content of the scroll view}
</ScrollView>
If by touch release you meant that there should be zero touches, you can check that with the event that is passed to onTouchEnd.
Not sure why you wanna access y offset in panResponder response. But there is a way to do it. First you need to define a local variable in your component like:
this.currentScrollViewX=0;
this.currentScrollViewY=0;
Normally, you can trace the scrollView offset when scroll event happening. You can add props in ScrollView like
<ScrollView onScroll = {e => this._onScroll(e)} ...>
...
</ScrollView>
then define _onScroll method in your component, update the currentScrollViewX and currentScrollViewY when scrolling happens
_onScroll(e){
this.currentScrollViewX = e.nativeEvent.contentOffset.x;
this.currentScrollViewY = e.nativeEvent.contentOffset.x;
}
Then in your onPanResponderRelease handler, you can always get the latest y offset by referring this.currentScrollViewY
If you wanna do drag and drop animation in ScrollView using PanResponder, it will have some issues when PanResponder drag and scrollView scroll.
More detailed is here https://github.com/facebook/react-native/issues/1046
Related
I want to have a carousel that each item is a zoomable image, the carousel should be all over the screen so I use Portal for that. To support zoom I use ImageZoom component from react-native-image-pan-zoom, and the carousel is from react-native-reanimated-carousel, in the following way:
<Portal>
<Carousel
loop
windowSize={1}
width={SCREEN_WIDTH}
height={SCREEN_HEIGHT}
data={images}
style={{
flex: 1,
backgroundColor: "black",
}}
defaultIndex={imageIndexToDisplay}
onSnapToItem={handleImageChange}
renderItem={({ item: { url, width, height } }) => (
<ImageZoom
cropWidth={SCREEN_WIDTH}
cropHeight={SCREEN_HEIGHT}
imageWidth={width}
imageHeight={height}
enableSwipeDown
onSwipeDown={handleClose}
onClick={handlePress}
useNativeDriver
>
<Image
imageSource={url}
defaultImage={defaultImage}
imageStyle={{ height, width }}
/>
</ImageZoom>
)}
/>
</Portal>
What happens is that the carousel barely let me scroll left or right since it seems like the ImageZoom responds first to the scrolls. I tried to set onStartShouldSetPanResponder={() => false} on the ImageView which solves the Carousel scrolling but doesn't let me use the ImageZoom to zoom since it appears like the Carousel now responds first to gestures.
I would like to know if there is any way to make them both work together.
Thanks ahead!
I need custom tooltip with semitransparent background which overlays the map.
So in common we draw first MapView. After marker press on top of MapView we draw overlay (backgroundColor: "#00000033"), on top of it draw tooltip and image over the marker position to simulate highlight. And now I need to get absolute position of marker on screen to draw my image over it. There is point prop in onPress nativeEvent regarding to docs but I haven't it in my real event and I don't know if that is what I need.
{
"action": "marker-press",
"coordinate": {
"latitude": -15.3469687,
"longitude": 37.100195
},
"id": "unknown",
"target": 295
}
Is there a way to get marker's on screen position?
Yes, you can use the measureInWindow method on the ref of the custom component. If your custom component doesn't have this method available, you can wrap it in a View, and use the method on that.
Example:
const markerRef = useRef();
const [markerPosition, setMarkerPosition] = useState({});
const measureMarker = () => {
markerRef.current?.measureInWindow((x, y, width, height) => {
const { width: screenWidth, height: screenHeight } = Dimensions.get('screen');
// if any non-zero, on-screen values, save measurements
if (!(x || y || width || height)) return;
if (x > screenWidth || y > screenHeight) return;
setMarkerPosition({x, y, width, height});
}
};
...
return (
<View ref={markerRef} onLayout={measureMarker}>
<CustomMarker
...
);
See the docs for measureInWindow for more. measure would also work for the purpose of getting the on-screen position.
Note that these callbacks won't work until native layout is completed.
A simple use-case is to allow a user to either click buttons to paginate in a slider, or drag. Both events call the same paginate function with a param to either go forward or back--simple stuff.
However, the trigger from drag seems to cause bizarre behavior where the slider wants to start the animation from several slides back as if it ignores the updated props. This doesn't happen when using the buttons and both use the same simple paginate call.
Any tips appreciated.
Minimal example:
export default function App() {
const [position, setPosition] = useState<number>(0);
const paginate = (direction: Direction) => {
setPosition((prev) => {
return direction === Direction.Forward
? Math.max(-800, prev - 200)
: Math.min(0, prev + 200);
});
};
return (
<div className="App">
<Slider>
<Wrapper
animate={{ x: position }}
transition={{
x: { duration: 1, type: "tween" }
}}
drag="x"
dragConstraints={{
top: 0,
left: 0,
right: 0,
bottom: 0
}}
onDragEnd={(e, { offset, velocity }) => {
const swipe = swipePower(offset.x, velocity.x);
if (swipe < -swipeConfidenceThreshold) {
paginate(Direction.Forward);
} else if (swipe > swipeConfidenceThreshold) {
paginate(Direction.Back);
}
}}
>
<Slide>1</Slide>
<Slide className="alt">2</Slide>
<Slide>3</Slide>
<Slide className="alt">4</Slide>
<Slide>5</Slide>
</Wrapper>
</Slider>
<button onClick={() => paginate(Direction.Back)}>prev</button>
<button onClick={() => paginate(Direction.Forward)}>next</button>
</div>
);
}
Codesandbox Demo
I have to say, this problem is quite interesting. However, I think I figured out a way for you to handle this. One thing I noticed is that if you comment out
onDragEnd={(e, { offset, velocity }) => {
// const swipe = swipePower(offset.x, velocity.x);
// if (swipe < -swipeConfidenceThreshold) {
// paginate(Direction.Forward);
// } else if (swipe > swipeConfidenceThreshold) {
// paginate(Direction.Back);
// }
}}
the entire onDragEnd prop function, this example still doesn't work, since by the looks of things, the draggable component is not respecting your offset.
I realized that at this point, the problem is the internal state of the component is out of sync with your state. And would you look at that, the Framer Motion API actually provides a way to inspect this.
https://www.framer.com/api/motion/motionvalue/#usemotionvalue
It's the hook useMotionValue() which allows us to see what's actually happening. Turns out, our value is being set wrong when the user starts dragging:
useEffect(
() =>
motionX.onChange((latest) => {
console.log("LATEST: ", latest);
}),
[]
);
We can see this, because the state "jumps" to 200 as soon as we start dragging.
So fixing in theory is easy, we just need to make sure to let that value "know" about our offset, and that way it's gonna start with the proper offset in mind!
Anyway, that was my thought process, here's the solution, all you need to do is set the left constraint to make it work:
dragConstraints={{
top: 0,
left: position,
right: 0,
bottom: 0
}}
And tada! This makes it work. Here's my working solution: https://codesandbox.io/s/lingering-waterfall-2tsfi?file=/src/App.tsx
I am trying to set up a slider inside a horizontal ScrollView that would allow me to scroll the page faster. I am able to link position of the page to the value of the slider, so that when I scroll the page, the thumb of the slider moves accordingly.
I am using React Native Slider and a ScrollView.
Here is the result that I am unfortunately having.
I am quite new to RN, so I am probably missing something important here.
class Comp extends Component {
state = {
width : 0,
value : 0,
cursor : 0
}
moveTheCursor = (val) => {
this.scrollView.scrollTo({x: val, y: 0, animated: true})
}
scrollTheView = (event) => {
this.setState({
value : Math.floor(event.nativeEvent.contentOffset.x),
cursor : Math.floor(event.nativeEvent.contentOffset.x)
})
}
checkWidth = ({nativeEvent}) => {
arrayWidth.push(nativeEvent.layout.width)
let ww = (arrayWidth[0] * this.props.data.length) - Math.round(Dimensions.get('window').width);
this.setState({
width : ww,
})
}
render() {
return (
<React.Fragment>
<ScrollView
ref={ref => this.scrollView = ref}
horizontal
style={styles.car}
scrollEventThrottle={1}
onScroll={this.scrollTheView}
showsHorizontalScrollIndicator={false}
decelerationRate={0}
//snapToInterval={200} //your element width
snapToAlignment={"center"}
>
</ScrollView>
<Slider
style={styles.containerSlide}
thumbImage={require("./../../assets/img/buttons/thumb.png")}
trackImage={require("./../../assets/img/buttons/bar.png")}
minimumValue={0}
maximumValue={this.state.width}
onValueChange={this.moveTheCursor}
value={this.state.cursor}
/>
</React.Fragment>
);
}
}
The problem is, when I use the thumb of the slider to scroll the page, it triggers the scroll that inevitably resets the position of the slider thumb, so it is not behaving correctly (flickers but it is mostly inaccurate).
Is there a way to fix this loop?
You can achieve desired behaviour using Slider's onSlidingStart and onSlidingComplete + ScrollView's scrollEnabled
It can look like
....
const [isSliding, setIsSliding] = useState(false);
....
<ScrollView scrollEnabled={!isSliding}>
<Slider
onSlidingStart={() => setIsSliding(true)}
onSlidingComplete={() => setIsSliding(false)}
/>
</ScrollView>
The customised events are rendered as expected, but the day cells don't grow in height to include the events at the initial load.
If I change the size of the window, or click on another view, and change back to the month view, it renders correctly. Then if an event is triggered (date click or event click), the calendar rerenders the same way as the initial load.
I tried rerendering the calendar in 'datesRender' function with this.render(), but it's not ideal to load everything twice, also it has a flicking effect when clicking on a date or event.
...
const renderEvent = ({ event, el }) => {
const eventComponent = (
<div className="wrapper">
Event content here
</div>
);
ReactDOM.render(eventComponent, el);
};
...
<FullCalendar
defaultView='dayGridMonth'
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
header={{
left: 'prevYear,nextYear',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay today prev,next'
}}
weekends={true}
events={events}
dateClick={handleDateClick}
handleWindowResize={true}
eventRender={renderEvent}
eventClick={function(info) {
handleEventClick(info.event.id);
}}
datesRender={function(info) {
// this.render();
}}
/>
...
I would like the calendar render properly with the initial load.