React Native 奇幻之旅(6)-內建組件 Image

React Native logo

終於到內建組件最後一天了!

今天要分享的是 Image 組件容易讓人混淆的一些用法,如果不是真的踩過這些坑的話光看官方文檔是很難一下就理解的 XD

圖像來源

圖像支持以下兩種類型的來源:

  • 網絡圖片 uri
  • 靜態圖像資源 require

支持的圖片格式:png、jpg、jpeg、bmp、gif、webp、psd(僅限 iOS)

<Image source={require('../../assets/icon.png')} />
<Image
    source={{
      uri: 'https://reactnative.dev/img/tiny_logo.png',
    }}
/>
<Image
    source={{
      uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==',
    }}
/>

但很神奇的是,以上這三張圖片,只有第一張會顯示。

DDPPIT3

這是因為使用 require 關鍵字導入靜態圖像資源的話 RN 會在編譯時獲取圖像的信息,因此不需要特別設置寬高,而 網絡圖片base64 這兩種方式則無法,需要你事先設置圖片的寬高,只設寬度或只設高度一樣不會顯示。

圖像尺寸

雖然官方文檔說有提供 width, height 的 prop,但實測這不適用於所有來源的圖片,比如說使用 require 關鍵字導入的靜態圖像資源就不適用:

<Image
    source={require('../../assets/icon.png')}
    // style={{ width: 100, height: 100 }}
    width={100}
    height={100}
/>

require 關鍵字導入的靜態圖像資源需要用 style={{ width: 100, height: 100 }} 這種方式設置圖片的寬高。

width, heightstyle
Sp1W5JNRaUDWbV

所以通常來說不管什麼圖像來源我都會使用 style 來設置圖像大小,而不是直接用 width, height 的 prop。

縮放模式(resizeMode)

Image 組件中有一個屬性 resizeMode 可以用於縮放圖片,可以設置以下幾種類型:

  • center:圖片在視圖中居中顯示,不進行縮放。
  • cover:圖片等比例縮放,多出視圖的部分會被裁剪掉。
  • contain:圖片等比例縮放,保持圖片在視圖內完整顯示,可能會在視圖周圍留有空白區域。
  • stretch:圖片會被強制拉伸以填充整個視圖。
  • repeat:圖片在視圖內平鋪重複顯示,以填滿整個視圖。
// https://reactnative.dev/img/tiny_logo.png 為 64*64
<View
  style={{
    width: 400,
    height: 700,
    backgroundColor: '#aaa',
    justifyContent: 'center',
    alignItems: 'center'
  }}
>
  <Text>Center</Text>
  <Image
    source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
    style={{ width: 200, height: 100, backgroundColor: 'white' }}
    resizeMode="center"
  />

  <Text>Cover</Text>
  <Image
    source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
    style={{ width: 200, height: 100, backgroundColor: 'white' }}
    resizeMode="cover"
  />

  <Text>Contain</Text>
  <Image
    source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
    style={{ width: 200, height: 100, backgroundColor: 'white' }}
    resizeMode="contain"
  />

  <Text>Stretch</Text>
  <Image
    source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
    style={{ width: 200, height: 100, backgroundColor: 'white' }}
    resizeMode="stretch"
  />

  <Text>Repeat</Text>
  <Image
    source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
    style={{ width: 200, height: 100, backgroundColor: 'white' }}
    resizeMode="repeat"
  />
</View>
0OLAzup

自動調整圖片尺寸

假設要限制多張尺寸不一的圖片最大只能為 100*100且多餘部分不能被裁剪的話,可以將圖片縮放至最大,並在圖片外加個 View 限制其大小為 100*100,然後圖片組件的 resizeMode 設為 contain

<View style={{ width: 100, height: 100 }}>
  <Image
    source={{ uri: data.assets?.image }}
    style={{ width: '100%', height: '100%' }}
    resizeMode="contain"
  />
</View>
EgyTZA9

還有另外一種方式是使用 onLayout

  • 藉由 View 的 onLayout 方法獲取父組件視圖的寬度
  • 計算視圖的寬度和圖片寬度的比值 ratio
  • 圖片寬度 = 視圖寬度; 圖片高度 = 圖片高度 * ratio
// ResponsiveImage.tsx
import React, { useMemo, useState } from "react"
import {
  StyleSheet,
  View,
  Image,
  ImageSourcePropType,
  LayoutChangeEvent,
} from "react-native"

interface ResponsiveImageProps {
  src: ImageSourcePropType
  imgWidth: number
  imgHeight: number
}

const ResponsiveImage = ({ src, imgWidth, imgHeight }: ResponsiveImageProps) => {
  const [containerWidth, setContainerWidth] = useState<number>(0)

  const onViewLayoutChange = (event: LayoutChangeEvent) => {
    const { width } = event.nativeEvent.layout
    setContainerWidth(width)
  }

  const imageStyles = useMemo(() => {
    const ratio = containerWidth / imgWidth
    return {
      width: containerWidth,
      height: imgHeight * ratio
    }
  }, [containerWidth])

  return (
    <View style={styles.container} onLayout={onViewLayoutChange}>
      <Image source={src} style={imageStyles} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    width: "100%"
  }
})

export default ResponsiveImage

如此一來圖片便能依據視圖大小自動縮放了:

  • containerWidth: 400
  • ratio: 400 / 1024 = 0.390625
  • 圖片高度: 1024 * 0.390625 = 400
  • 縮放後的圖片尺寸: { width: 400, height: 400 }
import { View } from "react-native"
import ResponsiveImage from "../components/ResponsiveImage"
// ...

<View
  style={{
    width: 400,
    height: 700,
    backgroundColor: '#aaa',
    justifyContent: 'center',
    alignItems: 'center'
  }}
>
  <ResponsiveImage
    src={require("../../assets/icon.png")}
    imgWidth={1024}
    imgHeight={1024}
  />
</View>

圖片快取

如果希望圖片在加載過一次之後就不再重複加載,能更快速地顯示,那就需要為圖片做快取。

內建的 Image 組件有提供 cache control,不過僅支持 iOS,等到第十九天資料持久化存儲的時候會再詳細的分享在 iOS & Android 做圖片快取的方法。

<Image
  style={{ width: 350, height: 150 }}
  source={{
    uri: 'https://unsplash.it/350/150',
    cache: 'only-if-cached',
  }}
/>

https://reactnative.dev/docs/images#cache-control-ios-only

在 RN 中使用 SVG

RN 默認不支持 SVG,但有現成的庫可以提供 SVG 支持,比如 react-native-svg

import * as React from 'react';
import Svg, { Circle, Rect } from 'react-native-svg';

export default function SvgComponent(props) {
  return (
    <Svg height="50%" width="50%" viewBox="0 0 100 100" {...props}>
      <Circle cx="50" cy="50" r="45" stroke="blue" strokeWidth="2.5" fill="green" />
      <Rect x="15" y="15" width="70" height="70" stroke="red" strokeWidth="2" fill="yellow" />
    </Svg>
  );
}

如果需要將一般的 svg 轉為 RN 可以用的 JSX 可以使用這個網站 SVGR Playground

m5My1TS
const SvgComponent = (props: SvgProps) => (
  <Svg {...props}>
    <Rect width="100%" height="100%" fill="red" />
    <Circle cx={150} cy={100} r={80} fill="green" />
    <Text x={150} y={125} fill="#fff" fontSize={60} textAnchor="middle">
      {"SVG"}
    </Text>
  </Svg>
)

// Usage
<SvgComponent width={320} height={240} />

推薦閱讀

參考資料

留言

目前沒有留言。

發佈留言

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