On web, you can get any element by its position:
document.elementFromPoint(x, y)
Is there something similar in React Native? Can it be done with a native bridge (Java/Objective C)?
I would like to get an element on screen by position X and Y (Ignoring empty places and following transformation or styling calculations like padding, margin, borderRadius) then set it a native prop or dispatch events.
OBS: I'm not referring to onLayout property or any other declared in the component's construction/render, my idea is from any X and Y position get an element that corresponds to these coordinates then get a reference to it. A use case for example: Create a virtual cursor that dispatch click events on correct components, following margin/padding and ignoring pointerEvents none.
Image Example
You can use onLayout prop of React Native components.
You should check this link
Yes you can get your current elemnts position like in. 2 ways,
onLayout
suppose on click of that element you want to know that location
assign a ref to that
const newRef = useRef();
onButtonPress = () => {
newRef?.current?.measureInWindow( (fx, fy, width, height, px, py) => {
console.log('Component width is: ' + width)
console.log('Component height is: ' + height)
console.log('X offset to frame: ' + fx)
console.log('Y offset to frame: ' + fy)
console.log('X offset to page: ' + px)
console.log('Y offset to page: ' + py)
})
}
const onLayout=(event)=> {
const {x, y, height, width} = event.nativeEvent.layout;
}
return(
<TouchableOpacity ref={useRef} onLayout={onLayout} onPress={onButtonPress} >
</TouchableOpacity>
)
Related
What I try:
I add in my code
`
newRef?.current?.measure( (fx, fy, width, height, px, py) => {
setY(fy)
})
`
With this way I get y-position of Item but after first windowHeight the measure start from begin. How can achieve that the measure continue in all ScrollView?
I need to get a list of components (x, y) at specified coordinates, preferably in order from children to grandparents. Would be awesome if absolutely positioned components were taken into account.
Think: which components (and in what order) would have the opportunity to react to a click/touch event at (x, y).
I am currently trying to build a mechanism that would keep track of every component that exists, and what are it's relations to other components (which one is parent, which one is children, so I can sort them). It's ugly, lots of edgecases, components that I wish were trackable need to know about it and cooperate, and I think I'm just reimplementing something that's already there.
EDIT: This is (probably) not about measuring. It's about asking "what components are currently rendered under pixel (123,456)"
measure is react native default method for any component to get update on it.
Below is the example which help you to get x and y coordinate:
class screenname extends React.Component {
render() {
return <View ref={view => { this.screenname = view; }} />
}
componentDidMount() {
// Print component dimensions to console
this.screenname.measure( (fx, fy, width, height, px, py) => {
console.log('Component width is: ' + width)
console.log('Component height is: ' + height)
console.log('X : ' + fx)
console.log('Y : ' + fy)
console.log('X offset to page: ' + px)
console.log('Y offset to page: ' + py)
})
}
}
To find position of children with respect to parent what you have to do is pass parent reference to measureLayout function like below:-
import {findNodeHandle} from 'react-native';
calculateDimensions(){
const handler = findNodeHandle(this.rootRef);
this.childRef.measureLayout(handler, ( X_Position, Y_Position, Width, Height ) =>
{
this.setState({
X_Dimension : X_Position,
Y_Dimension: Y_Position,
Child_View_Height: Height,
Child_View_Width: Width
});
});
}
//Parent with nested child
<View ref={(ref) => this.rootRef = ref}>
<View ref={(ref) => this.childRef = ref}>
</View>
</View>
Hope this will help
I'm trying to render an curved vertical list like this iOS component: https://github.com/makotokw/CocoaWZYCircularTableView
That component (written in Obj-c) iterates the visible cells when laying them out, and sets the frame (i.e. indent) using asin.
I know in React Native I can set the leftMargin style in the renderItem callback, but I can't figure out how to get the on-screen index of the item - all I have is the index into the source data. And also, at that point, I don't think I have access to the absolute position.
Any ideas?
The function you are looking for is
onViewableItemsChanged.
You can use it with viewabilityConfig which provides us with
minimumViewTime,viewAreaCoveragePercentThreshold,waitForInteraction
which can be set accordingly
const VIEWABILITY_CONFIG = {
minimumViewTime: 3000,
viewAreaCoveragePercentThreshold: 100,
waitForInteraction: true,
};
_onViewableItemsChanged = (info: {
changed: Array<{
key: string,
isViewable: boolean,
item: any,
index: ?number,
section?: any,
}>
}
){
//here you can have the index which is visible to you
}
<FlatList
renderItem={this.renderItem}
data={this.state.data}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={VIEWABILITY_CONFIG}
/>
Thanks for both answers.
What I have ended up doing is deriving the visible items using the scroll offset of the list. This is simple because the list items all have the same height.
I do this in the onScroll handler, and at that point I calculate the horizontal offset for each item (and I use leftMargin / rightMargin to render this). It's not perfect, but it does give me an elliptical list.
_handleScroll = (event) => {
const topItemIndex = Math.floor(event.nativeEvent.contentOffset.y / LIST_ITEM_HEIGHT);
const topItemSpare = LIST_ITEM_HEIGHT-(event.nativeEvent.contentOffset.y % LIST_ITEM_HEIGHT);
const positionFromEllipseTop = (forIndex-topItemIndex)*LIST_ITEM_HEIGHT+topItemSpare;
const positionFromOrigin = Math.floor(Math.abs(yRadius - positionFromEllipseTop));
const angle = Math.asin(positionFromOrigin / yRadius);
if (orientation === 'Left') {
marginLeft = 0;
marginRight = ((xRadius * Math.cos(angle)))-LIST_ITEM_HEIGHT;
alignSelf = 'flex-end';
}
else if (orientation === 'Right') {
marginLeft = (xRadius * Math.cos(angle))-LIST_ITEM_HEIGHT;
marginRight = 0;
alignSelf = 'flex-start';
}
}
React-native's FlatList component has a prop called onLayout. You can get the position of the component on screen with this prop.
onLayout
Invoked on mount and layout changes with:
{nativeEvent: { layout: {x, y, width, height}}}
This event is fired immediately once the layout has been calculated,
but the new layout may not yet be reflected on the screen at the time
the event is received, especially if a layout animation is in
progress.
My complete source code for this issue is posted as an Expo app: https://exp.host/#kevinoldlifeway/swipe-scrollview-of-webviews as well as on Github https://github.com/kevinold/swipe-scrollview-of-webviews
I am building a book view in React Native. Using a ScrollView, I would like to swipe left and right to navigate through the pages of a title that could have several hundred to several thousand.
Since that is the case, my goal is to only the minimal amount of data so that the user is able to swipe between pages, seeing the immediately previous and next pages.
I am loading a 3 element array like so:
[Previous, Current, Next]
That would be updated in the state by Redux (not used here to keep simple) and would re-render and refocus the list.
My goal is that my ScrollView is always "centered" on the "Current" page.
Page scrolls to the previous and next page are handled by a handleScroll method which loads the appropriate precomputed array so that the current page stays in focus, but the previous and next pages (offscreen) are updated appropriately.
handleScroll (event) {
//const x = event.nativeEvent.contentOffset.x;
const { activeIndex, scrollTimes } = this.state;
const windowWidth = Dimensions.get('window').width;
const eventWidth = event.nativeEvent.contentSize.width;
const offset = event.nativeEvent.contentOffset.x;
console.log('event width: ', eventWidth);
console.log('event offset: ', offset);
console.log('scrollTimes: ', scrollTimes);
//if (scrollTimes <= 1 ) return;
if (windowWidth + offset >= eventWidth) {
//ScrollEnd, do sth...
console.log('scrollEnd right (nextPage)', offset);
const nextIndex = activeIndex + 1;
console.log('nextIndex: ', nextIndex);
// Load next page
this.loadMore()
} else if (windowWidth - offset <= eventWidth) {
//ScrollEnd, do sth...
console.log('scrollEnd left (prevPage)', offset);
// Load prev page
this.loadPrev()
}
this.setState({ scrollTimes: scrollTimes + 1 });
}
I have tried to balance the "current" page using a combination of:
contentOffset={{ x: width, y: 0 }} on ScrollView
And
componentDidMount() {
// Attempt to keep "center" element in array as focused "screen" in the horizontal list view
this.scrollView.scrollTo({ x: width, y: 0, animated: false });
}
I've also tried to scrollTo in the callback after this.setState, but have not had any luck.
I'm wondering if this "centering" could be accomplished by using Animated.
I gave this a shot but I'm not entirely sure I understood the problem, and I'm not sure how well this would hold up.
Basically I just simplified the handleScroll function significantly. First checking if we were on a scroll completion and if so determining if when we landed on that screen it was the "previous" screen or "next" - do nothing if it's already the middle screen.
I think in your code the issue was that it would fire and load data if it was the middle screen, not just the first or last. Therefore it would fire twice for each transition.
Here's the handleScroll that I think will work for you.
handleScroll (event) {
const offset = event.nativeEvent.contentOffset.x;
const mod = offset % width;
if (mod === 0) { // only transition on scroll complete
if (offset === width * 2) { // last screen
console.log('load more')
this.loadMore();
this.scrollView.scrollTo({ x: width, y: 0, animated: false });
} else if (offset !== width) { // first screen
console.log('load prev')
this.loadPrev();
this.scrollView.scrollTo({ x: width, y: 0, animated: false });
}
}
}
And a Snack demoing it.
I need to find out position of item in the list view (like rectForRowAtIndexPath does for UITableView), how it is possible?
I use the built in onLayout function for a view. For instance:
<View onLayout={this.handleLayout}/>
Then your function will have:
handleLayout: function(event){
var {x, y, width, height} = event.nativeEvent.layout;
console.log('x pos:' + x + ' / y pos:' + y + ' / width:' + width + ' / height:' + height);
},
Hope this helps!
I believe that this will work:
make sure each item has unique ref prop (more about ref props in React documentation: https://facebook.github.io/react/docs/more-about-refs.html)
in your code whenever you need call "measure" method on this.refs.your_reference. Make sure it is in componentDidMount(), however there might be a need to (Warning! Hacks!) to call it via setTimeout method as described here React-Native : measure a View