Site icon May's Notes

React Native 奇幻之旅(12)-測量組件尺寸

React Native logo

這是我在2023第十五屆 iThome 鐵人賽發表的系列文章。https://ithelp.ithome.com.tw/users/20136637/ironman/6408

在 RN 中有兩種常見的測量組件尺寸方式:

  1. onLayout
  2. measure

onLayout

有些時候會需要知道當前視圖的實際尺寸為何就可以用到 onLayout

當組件佈局發生變化時(例如組件的尺寸、位置等改變),React Native 會調用 onLayout 函數,就能獲得組件的新尺寸。

RN 內建的以下幾個組件都具有 onLayout 屬性:

觸發時機

基本使用

onLayout?: ((event: LayoutChangeEvent) => void) | undefined

LayoutChangeEvent 中有 width, height, x, y 四個值,分別代表:

import { View, Image, Text, type LayoutChangeEvent } from "react-native"

export const App = () => {
  const onLayout = (event: LayoutChangeEvent) => {
    const { width, height, x, y } = event.nativeEvent.layout
    console.log(width, height, x, y); // 320 320 80 349.3333435058594
  }

  return (
    <View style={{ flex: 1 }}>
      <View style={{ backgroundColor: 'white', padding: 10 }} onLayout={onLayout}>
         <Image source={{ uri: 'https://placehold.co/300x300.png' }} width={300} height={300} />
      </View>
    </View>
  )
}

寬度與高度的計算

以剛剛的例子 console.log(width, height, x, y) 輸出 320 320 80 349.3333435058594

改變下方這些樣式都會影響 onLayout 重新計算 View 的大小和位置

封裝成hook

可以將獲取組件大小的邏輯抽象出來成一個 custom hook:

// hooks/useComponentSize.tsx
import { useState, useCallback } from 'react'
import { View, Image, type LayoutChangeEvent } from 'react-native'

export const useComponentSize = () => {
  const [size, setSize] = useState({ width: 0, height: 0 })

  const onLayout = useCallback((event: LayoutChangeEvent) => {
    const { width, height } = event.nativeEvent.layout
    setSize({ width, height })
  }, [])

  return [size, onLayout] as const
}

// App.tsx
export const App = () => {
  const [size, onLayout] = useComponentSize()

  return (
    <View style={{ backgroundColor: 'white', padding: 10 }} onLayout={onLayout}>
      <Image source={{ uri: 'https://placehold.co/300x300.png' }} width={300} height={300} />
    </View>
  )
}

measure

measure 跟 onLayout 一樣可以用於獲取組件尺寸和位置,不同的是 measure 會需要使用 ref 來獲得組件的引用,然後再使用 ref.measure 方法來獲取組件的尺寸和位置。

ref.current.measure((x, y, width, height, pageX, pageY) => { // ... })

import { useRef, useEffect } from "react"
import { View, Image } from "react-native"

export const ViewPage = () => {
  const viewRef = useRef(null)

  useEffect(() => {
    handleMeasure()
  }, [viewRef])

  const handleMeasure = () => {
    if (viewRef.current) {
      viewRef.current.measure((x, y, width, height, pageX, pageY) => {
        console.log(width, height, pageX, pageY)
      })
    }
  }

  return (
    <View ref={viewRef} style={{ backgroundColor: 'white', padding: 10 }}>
      <Image source={{ uri: 'https://placehold.co/300x300.png' }} width={300} height={300} />
    </View>
  )
}

判斷內容長度是否需要滾動

如果希望頁面內容高度在小於設備屏幕高度時無需滾動,但在頁面內容高度小於設備屏幕高度時要可以滾動的話,就可以使用 onLayout 來和當前設備屏幕高度做比較:

import React, { useState } from 'react'
import { Platform, useWindowDimensions } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'

export const ContentLayout = () => {
  const { height } = useWindowDimensions()
  const [contentHeight, setContentHeight] = useState(0)

  const onLayout = (event: any) => {
    const { height } = event.nativeEvent.layout
    setContentHeight(height)
  }

  return (
    <ScrollView
      contentContainerStyle={{ flexGrow: 1 }}
      enabled={contentHeight > height}
      onLayout={onLayout}
    >
      {children}
    </ScrollView>
  )
}

總結

Exit mobile version