RN | 系统组件之 Image、ImageBackground 📝

426 阅读6分钟

Image 组件

常用属性和方法

interface ImageProps {
  // 源
  source: ImageSourcePropType;
  defaultSource?: ImageSourcePropType;

  // 样式
  style?: StyleProp<ImageStyle>;
  resizeMode?: 'cover' | 'contain' | 'stretch' | 'center' | 'repeat';
  blurRadius?: number;
  
  // 加载回调
  onLoadStart?: () => void;
  onLoadEnd?: () => void;
  onLoad?: (event: any) => void;
  onError?: (event: any) => void;
  onProgress?: (event: any) => void;

  // 其他
  fadeDuration?: number;  // Android only
  progressiveRenderingEnabled?: boolean;  // iOS only
  loadingIndicatorSource?: ImageSourcePropType;
}

// 静态方法
interface ImageStatic {
  // 预加载
  prefetch: (url: string) => Promise<boolean>;
  
  // 查询缓存
  queryCache: (urls: string[]) => Promise<{ [key: string]: string }>;
  
  // 获取图片尺寸
  getSize: (
    uri: string,
    success: (width: number, height: number) => void,
    failure?: (error: any) => void
  ) => void;
}

本地图片、网络图片、默认图片

import React from 'react';
import {Image, View, StyleSheet} from 'react-native';
import {Image1, Image2} from '../constants/images';
import LocalImage from './../assets/images/1.jpeg';

export default () => {
  return (
    <View style={styles.container}>
      {/* 本地图片 */}
      <Image source={LocalImage} style={styles.image} />

      {/* 网络图片 */}
      <Image
        source={{
          uri: `${Image1}`,
        }}
        style={styles.image}
      />

      {/* 带默认图的网络图片 */}
      <Image
        defaultSource={LocalImage}
        source={{uri: `${Image2}`}}
        style={styles.image}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  image: {
    width: 200,
    height: 200,
    marginVertical: 10,
  },
});

image.png

调整大小和缩放模式

cover 模式

export default () => {
  return (
    <View>
      {/* cover 模式 */}
      <View style={styles.wrapper}>
        <Image source={LocalImage1} style={styles.image} resizeMode="cover" />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  wrapper: {
    width: '100%',
    height: '100%',
    backgroundColor: '#C9CEC9',
  },
  image: {
    width: 200,
    height: 200,
  },
});

image.png

contain 模式

export default () => {
  return (
    <View>
      {/* contain 模式 */}
      <View style={styles.wrapper}>
        <Image source={LocalImage1} style={styles.image} resizeMode="contain" />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  wrapper: {
    width: '100%',
    height: '100%',
    backgroundColor: '#C9CEC9',
  },
  image: {
    width: 200,
    height: 200,
  },
});

image.png

stretch 模式

export default () => {
  return (
    <View>
      {/* stretch模式 */}
      <View style={styles.wrapper}>
        <Image source={LocalImage1} style={styles.image} resizeMode="stretch" />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  wrapper: {
    width: '100%',
    height: '100%',
    backgroundColor: '#C9CEC9',
  },
  image: {
    width: 200,
    height: 200,
  },
});

image.png

center 模式

export default () => {
  return (
    <View>
      {/* center 模式 */}
      <View style={styles.wrapper}>
        <Image source={LocalImage1} style={styles.image} resizeMode="center" />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  wrapper: {
    width: '100%',
    height: '100%',
    backgroundColor: '#C9CEC9',
  },
  image: {
    width: 200,
    height: 200,
    backgroundColor: '#fff',
  },
});

image.png

repeat 模式


export default () => {
  return (
    <View>
      {/* repeat模式 */}
      <View style={styles.wrapper}>
        <Image source={LocalImage1} style={styles.image} resizeMode="repeat" />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  wrapper: {
    width: '100%',
    height: '100%',
    backgroundColor: '#C9CEC9',
  },
  image: {
    width: 200,
    height: 200,
    backgroundColor: '#fff',
  },
});

image.png

blurRadius

用于给图片添加高斯模糊效果。值越高越模糊

blurRadius 的基本限制:

  • 最小值:0(无模糊)
  • 最大值:没有严格限制,但建议不超过 25
  • 只接受数值类型
<Image
  source={LocalImage1}
  style={styles.image}
  resizeMode="center"
  blurRadius={8}
/>

image.png

fadeDuration

渐入动画时间,用于控制图片淡入动画的持续时间(以毫秒为单位)。这个属性仅在 Android 平台上可用。

fadeDuration 的基本限制:

  • 最小值:0(立即显示,无淡入效果)
  • 默认值:300
  • 最大值:理论上没有严格限制,但建议不超过 1000-1500ms

推荐使用范围:

const FadeDuration = {
  NONE: 0,           // 无淡入效果
  FAST: 200,         // 快速淡入
  DEFAULT: 300,      // 默认淡入时间
  MEDIUM: 500,       // 中等淡入时间
  SLOW: 750,         // 慢速淡入
  MAX: 1000          // 建议最大值
};

实际使用建议:

  • 列表/网格:200-300ms
  • 预览图:300-500ms
  • 背景切换:500-1000ms

加载回调

  • onLoadStart: 开始加载图片时触发
  • onLoad:加载成功触发
  • onError:加载失败触发
  • onLoadEnd: 图片加载完成或失败时触发(无论成功还是失败都会触发)

onLoad

import LocalImage1 from '../assets/images/1.jpeg';
...
...
  <View style={styles.wrapper}>
    <Image
      source={LocalImage1}
      onLoad={event => {
        console.log(event.nativeEvent);
      }}
    />
  </View>
...
// onLoad
// event.nativeEvent
{"source": {"height": 525, "uri": "http://10.0.2.2:8081/assets/src/assets/images/1.jpeg?platform=android&hash=c93e2c55e5d5cfaae84dd939b3eed8d5", "width": 525}}

onError

什么时候才会触发 onError?

要触发 onError,需要提供一个格式正确但无法访问或不存在的图片 URL

// 使用一个不存在的完整 URL
source={{ uri: 'https://example.com/non-existent-image.jpg' }}
// 或者一个错误格式的 URL
// source={{ uri: 'https://broken-url' }}
  • URI 需要是有效的 URL 格式
  • 比如单个字符 'a' 不是有效的 URL
  • React Native 可能在验证 URL 格式时就拦截了这种明显无效的 URI
  1. 不触发 onError
  <View style={styles.wrapper}>
    <Image
      source={{uri: 'example.jpg'}}
      onLoad={event => {
        console.log(event.nativeEvent);
      }}
      onError={event => {
        console.log(event.nativeEvent);
      }}
    />
  </View>
// 触发的是onLoad
{"source": {"height": 525, "uri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "width": 525}}
  1. 触发 onError
  <View style={styles.wrapper}>
    <Image
      source={{uri: 'https://example.com/non-existent-image.jpg'}}
      onLoad={event => {
        console.log(event.nativeEvent);
      }}
      onError={event => {
        console.log(event.nativeEvent);
      }}
    />
  </View>
// onError
{"error": "Unexpected HTTP code Response{protocol=h2, code=404, message=, url=https://example.com/non-existent-image.jpg}"}

onLoadStart

没有event.nativeEvent

onLoadEnd

没有event.nativeEvent

tintColor

用于给图片添加着色效果。它主要用于改变图片的颜色,特别适用于图标和单色图片。

  • tintColor 主要用于改变图片颜色
  • 最适合用于单色图标和 PNG 透明图片
  • 可以使用任何有效的颜色值(名称、十六进制、rgba)
  • 常用于主题切换和状态指示
const IconExample = ({ active }) => {
  return (
    <Image 
      source={require('./assets/tab-icon.png')}
      style={styles.icon}
      tintColor={active ? '#007AFF' : '#8E8E93'}
    />
  );
};
// tintColor 最好用于带透明度的 PNG 图片
const TransparentIcon = () => {
  return (
    <Image 
      source={require('./transparent-icon.png')} // PNG with transparency
      tintColor="#007AFF"
    />
  );
};

Image API

  • getSize,用于在加载图片前获取图片的尺寸,不能用于本地图片
import {Image1} from '../constants/images';
...
...
  useEffect(() => {
    Image.getSize(
      Image1,
      (width, height) => {
        console.log('success load Image1: ', width, height);
      },
      error => {
        console.log(error);
      },
    );
  }, []);
...
  • prefetch,用于预加载图片。返回一个 Promise
Image.prefetch(Image1)
  .then(data => {
    console.log('prefetch Iamge1 result: ', data);
  })
  .catch(e => {
    console.log(e);
  });
  • queryCache,用于查询图片的缓存状态。它接收一个 URL 数组,返回一个 Promise,解析为一个对象,包含每个 URL 的缓存状态。
const cached = await Image.queryCache(urls);
console.log('缓存状态:', cached);
// cached 可能的结果:
// {
//   'https://example.com/image1.jpg': 'memory', // 在内存中
//   'https://example.com/image2.jpg': 'disk',    // 在磁盘中
//   'https://example.com/image3.jpg': 'undefined' // 图片未缓存
// }

例子,只预加载未缓存的图片:

const SmartPreloader = ({ imageUrls }) => {
  const preloadImages = async () => {
    try {
      // 检查哪些图片已经缓存
      const cacheStatus = await Image.queryCache(imageUrls);
      
      // 只预加载未缓存的图片
      const needsPreload = Object.entries(cacheStatus)
        .filter(([_, status]) => !status)
        .map(([url]) => url);

      if (needsPreload.length > 0) {
        console.log(`预加载 ${needsPreload.length} 张图片`);
        await Promise.all(
          needsPreload.map(url => Image.prefetch(url))
        );
      }
    } catch (error) {
      console.error('预加载失败:', error);
    }
  };

  useEffect(() => {
    preloadImages();
  }, [imageUrls]);

  return null;
};

ImageBackground

ImageBackground 是 React Native 中用于创建带背景图片的容器组件。它实际上是 Image 和 View 的组合。

例子:

import React from 'react';
import {ImageBackground, Text, StyleSheet} from 'react-native';
import LocalImage3 from '../assets/images/3.jpeg';

export default () => {
  return (
    <ImageBackground
      source={LocalImage3}
      style={styles.background}
      resizeMode="cover">
      <Text style={styles.text}>Hello World</Text>
    </ImageBackground>
  );
};

const styles = StyleSheet.create({
  background: {
    width: '100%',
    height: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'red',
  },
  text: {
    color: 'white',
    fontSize: 24,
    fontWeight: 'bold',
  },
});

image.png

常见属性

const ImageBackgroundProps = () => {
  return (
    <ImageBackground
      // 图片源
      source={require('./background.jpg')}
      // 样式
      style={styles.background}
      // 图片样式
      imageStyle={styles.image}
      // 调整大小模式
      resizeMode="cover"
      // 模糊效果
      blurRadius={5}
      // 加载指示器
      loadingIndicatorSource={require('./loading.gif')}
      // 图片加载事件
      onLoadStart={() => console.log('开始加载')}
      onLoad={() => console.log('加载完成')}
      onError={() => console.log('加载失败')}
    >
      {/* 子组件 */}
    </ImageBackground>
  );
};

imageStyle 和 style 属性的区别

  • style: 应用于容器组件(外层View)的样式

  • imageStyle: 应用于背景图片(Image)的样式

import React from 'react';
import { ImageBackground, Text, StyleSheet } from 'react-native';

const StyleDifferenceExample = () => {
  return (
    <ImageBackground
      source={require('./background.jpg')}
      // style 控制整个容器
      style={{
        width: 300,
        height: 200,
        justifyContent: 'center',
        alignItems: 'center',
        borderWidth: 2,
        borderColor: 'black',
        margin: 10,
      }}
      // imageStyle 只控制背景图片
      imageStyle={{
        borderRadius: 20,
        opacity: 0.7,
        tintColor: 'gray',
      }}
    >
      <Text>Hello World</Text>
    </ImageBackground>
  );
};

Image 和 ImageBackground 的区别

  1. 基本定义:

    • Image 用于显示图片的基础组件
    • ImageBackground 可以包含其他组件的图片容器
  2. 组件结构和样式:

    • Image 单一组件,单一 style 属性
    • ImageBackground 实际上是 Image + View 的组合,style 和 imageStyle 两个样式属性
  3. 性能考虑:

    • Image: 较轻量
    • ImageBackground: 较重,需要额外的 View 包装
  4. 应用场景:

    • Image: 图片、图标、头像等
    • ImageBackground: 登录页面、卡片背景等
  5. 功能上:

    • Image: 主要用于图片显示
    • ImageBackground: 可以添加遮罩、渐变等效果