React native 按比例显示图片

399 阅读2分钟

最近使用 RN 做图片显示的功能,需要图片固定宽度,然后按比例显示,本来以为设置了 widthresizeMode 就可以,实际测试并不是如此,要么就是显示不全,要么就是宽度达不到要求;实际发现,获取图片的比例,然后计算出高度,才是可行的做法

功能实现

image.png

react native 的 source 参数支持传入三种类型,但实际上 ImageURISource[] 基本没看到有这种用法,也没看到官网有对应的介绍,实际测试发现传数组确实可以显示图片,但却只会显示第一张图片,为了安全,我们仍做兼容

本地图片和网络图片获取宽高用的是不同的 api,本地用的是 Image.resolveAssetSource,网络用的是 Image.getSize,因此需要判断传入的 source 是哪种类型,是本地图片还是网络图片,搭配 typescript 的 is 关键字,可以写出

import { ImageRequireSource, ImageSourcePropType, ImageURISource } from 'react-native';

const isURISourceArray = (source: ImageSourcePropType): source is ImageURISource[] => {
	return Array.isArray(source);
};

const isURISource = (source: ImageSourcePropType): source is ImageURISource => {
	return !Array.isArray(source) && typeof source !== 'number';
};

const isRequireSource = (source: ImageSourcePropType): source is ImageRequireSource => {
	return typeof source === 'number';
};

以下是最终的代码

import { useEffect, useState } from 'react';
import { Image, ImageProps, ImageRequireSource, ImageSourcePropType, ImageURISource } from 'react-native';

export default const ScaleImage = ({ desiredWidth, ...imageProps }: ScaleImageProps) => {
    const [desiredHeight, setDesiredHeight] = useState(0);
    const { source } = imageProps;

    useEffect(() => {
        if (isRequireSource(source)) {
            const { width, height } = Image.resolveAssetSource(source);
            setDesiredHeight((desiredWidth * height) / width);
        } else if (isURISource(source)) {
            Image.getSize(source.uri as string, (width, height) => {
                setDesiredHeight((desiredWidth / width) * height);
            });
        } else if (isURISourceArray(source)) {
            Image.getSize(source[0]?.uri as string, (width, height) => {
                setDesiredHeight((desiredWidth / width) * height);
            });
        }
    }, [source, desiredWidth]);

    return (
        <Image
            {...imageProps}
            style={[
                imageProps.style,
                {
                    width: desiredWidth,
                    height: desiredHeight,
                },
            ]}
        />
    );
};

interface ScaleImageProps extends ImageProps {
    desiredWidth: number;
}

const isURISourceArray = (source: ImageSourcePropType): source is ImageURISource[] => {
    return Array.isArray(source);
};

const isURISource = (source: ImageSourcePropType): source is ImageURISource => {
    return !Array.isArray(source) && typeof source !== 'number';
};

const isRequireSource = (source: ImageSourcePropType): source is ImageRequireSource => {
    return typeof source === 'number';
};

优化

绝大部分情况下,我们是能知道传入的是本地图片还是网络图片的,那就不必做判断的步骤了,因此可以分为两个组件

渲染本地图片

import { Image, ImageRequireSource, ImageStyle, StyleProp } from 'react-native';

export default function LocalImage({ source, desiredWidth, style }: LocalImageProps) {
	const { width, height } = Image.resolveAssetSource(source);

	return (
		<SKComponent.Image
			source={source}
			style={[
				{
					width: desiredWidth,
					height: (desiredWidth * height) / width,
				},
				style,
			]}
		/>
	);
}

interface LocalImageProps {
	source: ImageRequireSource;
	desiredWidth: number;
	style?: StyleProp<ImageStyle>;
}

渲染远程图片

import { useEffect, useState } from 'react';
import { Image, ImageStyle, StyleProp } from 'react-native';

export default function RemoteImage({ uri, desiredWidth, style }: RemoteImageProps) {
	const [desiredHeight, setDesiredHeight] = useState(0);

	useEffect(() => {
		Image.getSize(uri, (width, height) => {
			setDesiredHeight((desiredWidth / width) * height);
		});
	}, [uri, desiredWidth])

	return (
		<SKComponent.Image
			source={{ uri }}
			style={[
				{
					width: desiredWidth,
					height: desiredHeight,
				},
				style,
			]}
		/>
	);
}

interface RemoteImageProps {
	uri: string;
	desiredWidth: number;
	style?: StyleProp<ImageStyle>;
}