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 屬性:

  • Image
  • Pressable
  • ScrollView
  • Text
  • TextInput
  • TouchableWithoutFeedback
  • View

觸發時機

  • 當組件首次被渲染並且佈局完成後。
  • 佈局發生了變化,例如其子組件的尺寸變化、組件被添加或移除等情況。
  • 從豎屏切換到橫屏。

基本使用

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

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

  • 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

  • width: 320 代表圖片寬度 300 + 水平 padding 10*2 = 20
  • height 同理
  • 這邊父視圖是全屏,所以 x: 80, y: 349.333 就代表 View 在距離原點(左上角)的距離
ignwagP

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

  • padding
  • borderWidth
  • left, top, right, bottom
  • height, width, maxHeight, maxWidth
  • aspectRatio

封裝成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) => { // ... })

  • width, height: 元素的寬高
  • pageX, pageY: 相對於整個頁面的位置
  • x, y: 相對於父組件的位置
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 來和當前設備屏幕高度做比較:

  • 獲取設備屏幕高度:useWindowDimensions
  • 獲取頁面內容高度:onLayout
  • 當頁面內容高度超過設備屏幕高度時才可以滾動 enabled={contentHeight > height}
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>
  )
}

總結

  • onLayout
    • 用法:onLayout 屬性
    • 使用時機:需監聽組件的佈局變化,實時獲取組件的大小和位置
  • measure
    • 用法:ref
    • 使用時機:在指定時機獲取組件尺寸和位置,比起 onLayout 還多回傳 pageX, pageY

留言

目前沒有留言。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *