Native reagieren: Die Position eines Elements ermitteln

78

Ich gestalte eine ImageKomponente mit Flexbox so, dass sie sich in der Mitte des Bildschirms befindet, was ziemlich gut funktioniert. Jetzt möchte ich, dass eine zweite ImageKomponente direkt über der ersten angezeigt wird. Das zweite Bild verwendet die absolute Positionierung. Momentan rate ich nur Pixel, damit sie passen, aber das ist natürlich nicht genau und viel zu viel Aufwand für die Wartbarkeit.

Ich bin ziemlich auf der Suche nach dem React Native-Äquivalent von jQuery's .offset(). Gibt es so etwas und wenn nicht, wie kann man das am besten erreichen?

Johannes Stein
quelle

Antworten:

100

React Native bietet eine .measure(...)Methode, die einen Rückruf entgegennimmt und ihn mit den Offsets und der Breite / Höhe einer Komponente aufruft:

myComponent.measure( (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)
})

Beispiel...

Im Folgenden wird das Layout einer benutzerdefinierten Komponente nach dem Rendern berechnet:

class MyComponent extends React.Component {
    render() {
        return <View ref={view => { this.myComponent = view; }} />
    }
    componentDidMount() {
        // Print component dimensions to console
        this.myComponent.measure( (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)
        })        
    }
}

Fehlerhinweise

  • Beachten Sie, dass die Komponente manchmal das Rendern nicht beendet, bevor sie componentDidMount()aufgerufen wird. Wenn Sie als Ergebnis Nullen erhalten measure(...), setTimeoutsollte das Problem durch Einwickeln in a gelöst werden, dh:

    setTimeout( myComponent.measure(...), 0 )
    
tohster
quelle
4
Diese Art der Anrufung von measurehat bei mir nicht funktioniert. Musste Element Handle übergeben:const handle = findNodeHandle(this.refs.dropdown); UIManager.measure(handle, (x, y, ...) ...)
Brock
setImmediate (...) ersetzt jetzt setTimeout (..., 0). Es hat das gleiche Verhalten, bietet jedoch eine gewisse Semantik für Ihr Anruf-Timing.
JesusTheHun
@tohster Zum ersten Mal scrollen Sie in die richtige Position. Danach ist der Y-Versatz zum Seitenwert jedoch nicht korrekt. Weißt du warum es passiert?
Balasubramanian
onLayout ist der bevorzugte Weg, dies zu tun
hakazvaka
Funktioniert das auf Android? Ich habe versucht, die Position relativ zum Elternteil (fy) zu bekommen und 0 zu bekommen. Auf ios funktioniert gut.
Victor Molina
56

Sie können verwenden onLayout, um die Breite, Höhe und die Position einer Komponente relativ zur übergeordneten Position zum frühestmöglichen Zeitpunkt abzurufen:

<View
  onLayout={event => {
    const layout = event.nativeEvent.layout;
    console.log('height:', layout.height);
    console.log('width:', layout.width);
    console.log('x:', layout.x);
    console.log('y:', layout.y);
  }}
>

Im Vergleich zu verwenden , .measure()wie in der akzeptierten Antwort gezeigt , hat dies den Vorteil , dass Sie nie Ihre Geige haben um Aufschub .measure()Anrufe mit , setTimeoutum sicherzustellen , dass die Messungen zur Verfügung stehen, aber der Nachteil , dass es nicht Ihnen Offsets zu dem relativ nicht geben ganze Seite, nur solche relativ zum übergeordneten Element.

Mark Amery
quelle
16
Sie können die Antworten kombinieren - in der onLayout-Aufrufmaßnahme ()
Juozas Kontvainis
Das ist so viel prägnanter als mit Timeouts zu spielen!
Night Warrier
22

Ich musste die Position eines Elements in einer ListView finden und verwendete dieses Snippet, das wie folgt funktioniert .offset:

const UIManager = require('NativeModules').UIManager;
const handle = React.findNodeHandle(this.refs.myElement);
UIManager.measureLayoutRelativeToParent(
  handle, 
  (e) => {console.error(e)}, 
  (x, y, w, h) => {
    console.log('offset', x, y, w, h);
  });

Dies setzt voraus, dass ich eine ref='myElement'auf meiner Komponente hatte.

Herr Sprecher
quelle
3
Beachten Sie, dass Sie - wie bei der akzeptierten Antwort - den Anruf möglicherweise UIManager.measureLayoutRelativeToParentin einen setTimeoutAnruf einschließen müssen, um zu verhindern, dass er fehlerhaft wird, wenn Sie diesen Code an einem Punkt im Lebenszyklus der Komponente ausführen, bevor er gerendert wurde. Beachten Sie auch, dass Sie für das moderne React Native import { UIManager, findNodeHandler } from 'react-native'eher die hier gezeigten Anforderungen als die hier gezeigten erfüllen möchten .
Mark Amery
1
UIManagerund findNodeHandlekann jetzt direkt aus "react-native" importiert werden. dhimport { UIManager, findNodeHandle} from "react-native"
HBSKan
19

Ich hatte ein ähnliches Problem und löste es, indem ich die obigen Antworten kombinierte

class FeedPost extends React.Component {
  constructor(props) {
    ...
    this.handleLayoutChange = this.handleLayoutChange.bind(this);
  }


handleLayoutChange() {
    this.feedPost.measure( (fx, fy, width, height, px, py) => {
      console.log('Component width is: ' + width)
      console.log('Component height is: ' + height)
      console.log('X offset to page: ' + px)
      console.log('Y offset to page: ' + py)
    })
  }

  render {
    return(
      <View onLayout={(event) => {this.handleLayoutChange(event) }} 
      ref={view => { this.feedPost = view; }} >
...

Jetzt kann ich die Position meines feedPost-Elements in den Protokollen sehen:

08-24 11:15:36.838  3727 27838 I ReactNativeJS: Component width is: 156
08-24 11:15:36.838  3727 27838 I ReactNativeJS: Component height is: 206
08-24 11:15:36.838  3727 27838 I ReactNativeJS: X offset to page: 188
08-24 11:15:36.838  3727 27838 I ReactNativeJS: Y offset to page: 870
Mislavoo7
quelle
1
Woher kommt die Variable this.feedPost? Initialisieren Sie es irgendwo? Ich werde undefiniert ist keine Funktion this.feedPost.measure sofort, wenn ich meine App
ausführe
In der Render-Methode im Rückgabeteil habe ich die <View onLayout {(event) => {this.handleLayoutChange(event) }} ref={view => { this.feedPost = view; }} > .... Stammansicht : Diese Hauptansicht ist this.feedPost.
Mislavoo7
@jsparks feedPost ist der Klassenname: Klasse FeedPost
Rickard
Dies ist ein Effekt bei der Verwendung von Hooks und die einzige Möglichkeit, die x / y-Position einer Seite oder eines Bildschirms zu ermitteln
Kerkness,
3

Dies scheint sich in der neuesten Version von React Native geändert zu haben, wenn refs zur Berechnung verwendet werden.

Deklarieren Sie Refs auf diese Weise.

  <View
    ref={(image) => {
    this._image = image
  }}>

Und finden Sie den Wert auf diese Weise.

  _measure = () => {
    this._image._component.measure((width, height, px, py, fx, fy) => {
      const location = {
        fx: fx,
        fy: fy,
        px: px,
        py: py,
        width: width,
        height: height
      }
      console.log(location)
    })
  }
Brian F.
quelle
1
-1; Ich kann die Notwendigkeit der Verwendung ._componentin React Native 0.51 (aktuelle neueste Version, veröffentlicht 3 Tage bevor Sie diese Antwort veröffentlicht haben) nicht wiederholen . Ich bin mir nicht sicher, woher du das bekommst. für mich das erste Argument des refRückruf ist eine Komponente mit einem .measureVerfahren.
Mark Amery
2
@MarkAmery Sie benötigen es, wenn Sie beispielsweise Animated.Viewanstelle von verwenden View.
Elzi