使用React Native自动缩放图像高度

101
在我的React Native应用中,我从一个具有未知尺寸的API中获取图像。如果我知道所需的宽度,如何自动缩放高度? 例如:我将宽度设置为Dimensions.get('window').width。如何设置高度并保持相同的比例?
export default class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      imgUrl: 'http://someimg.com/coolstuff.jpg'
    }
  }

  componentDidMount() {
    // sets the image url to state
    this.props.getImageFromAPi()
  }

  render() {
    return (
      <View>
        <Image 
          source={uri: this.state.imgUrl}
          style={styles.myImg}
        />
        <Text>Some description</Text>
      </View>
    )
  }
}

const styles = StyleSheet.create(
  myImg: {
    width: Dimensions.get('window').width,
    height: >>>???what goes here???<<<
  }
)

1
请查看react-native-scalable-image - Ihor Burlachenko
请看react-native-auto-height-image。 - Amit Bravo
22个回答

89

试一下这个:

 import React, { Component, PropTypes } from "react";
 import { Image } from "react-native";

export default class ScaledImage extends Component {
constructor(props) {
    super(props);
    this.state = { source: { uri: this.props.uri } };
}

componentWillMount() {
    Image.getSize(this.props.uri, (width, height) => {
        if (this.props.width && !this.props.height) {
            this.setState({
                width: this.props.width,
                height: height * (this.props.width / width)
            });
        } else if (!this.props.width && this.props.height) {
            this.setState({
                width: width * (this.props.height / height),
                height: this.props.height
            });
        } else {
            this.setState({ width: width, height: height });
        }
    });
}

render() {
    return (
        <Image
            source={this.state.source}
            style={{ height: this.state.height, width: this.state.width }}
        />
    );
}
}

ScaledImage.propTypes = {
uri: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number
};

我正在将URL作为名为uri的属性传递。您可以将width属性指定为Dimensions.get('window').width,然后就完成了。

请注意,如果您知道要设置的高度并且需要调整宽度以保持比例,则这也适用。在这种情况下,您将指定height属性而不是width属性。


1
这个可行,非常感谢。让我惊讶的是没有内置的方法来完成这个任务。我猜 RN 还是相当新的。 - Phil Mok
1
@plmok61,你也可以尝试在Image类中使用resizeMode属性,并在样式中应用flex。我最初尝试了这种方法,但我不喜欢容器的高度不会根据图像重新缩放而扩展的方式。 - TheJizel
在哪里添加实际的图像URL路径? - Somename
@Somename 将其传递给 uri 属性。 - TheJizel
抱歉,我是RN的新手。仍然无法理解。已经多次阅读了文档。请解释实际的网络URL放在哪里。非常感谢。 - Somename
显示剩余3条评论

51

有一个属性resizeMode,将其设置为'contain'

例如:

<Image
    source={require('./local_path_to/your_image.png')}
    style={{ width: 30 }}
    resizeMode="contain"
 />

来源:https://facebook.github.io/react-native/docs/image#resizemode

编辑:以上解决方案适用于我,resizeMode 属性未被弃用,我找不到任何迹象表明他们计划这样做。如果由于某种原因上述解决方案对您无效,您可以自行计算高度。以下是一个例子:

const Demo = () => {
    const scaleHeight = ({ source, desiredWidth }) => {
        const { width, height } = Image.resolveAssetSource(source)

        return desiredWidth / width * height
    }

    const imageSource = './local_image.png'
    const imageWidth = 150
    const imageHeigh = scaleHeight({
        source: require(imageSource),
        desiredWidth: imageWidth
    })
    
    return (
        <View style={{
            display: 'flex',
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center'
        }}>
            <Image
                source={require(imageSource)}
                style={{
                    borderWidth: 1,
                    width: imageWidth,
                    height: imageHeigh
                }}
            />
        </View>
    )
}

上述解决方案仅适用于本地图像。以下是如何处理远程图像:

const RemoteImage = ({uri, desiredWidth}) => {
    const [desiredHeight, setDesiredHeight] = React.useState(0)

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

    return (
        <Image
            source={{uri}}
            style={{
                borderWidth: 1,
                width: desiredWidth,
                height: desiredHeight
            }}
        />
    )
}

const Demo = () => {
    return (
        <View style={{
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center'
        }}>
            <RemoteImage
                uri="https://via.placeholder.com/350x150"
                desiredWidth={200}
            />
        </View>
    )
}

2
它可以自动缩放图像以适应定义的大小,这可能有助于通过一些思考解决上述问题 - 对于我那些未知尺寸的图像确实有所帮助。 - Toni Leigh
似乎无法与最新版本一起使用。 - Shivam
1
@Shivam,我今天检查了一下,它运行良好。我更新了我的答案,并提供了解决问题的其他方法的示例。希望能有所帮助! - Nedko Dimitrov
@Underdog,你的“expo”和“react”版本并不重要。至于“react-native”,我今天检查了一下,resizeMode属性并没有被弃用,也没有任何迹象表明他们计划这样做。 - Nedko Dimitrov
@NedkoDimitrov 好的,我正在尝试远程图片。 - Shivam

9

请看这个库 react-native-scalable-image,它正好可以实现你所要求的功能。

import React from 'react';
import { Dimensions } from 'react-native';
import Image from 'react-native-scalable-image';

const image = (
   <Image
       width={Dimensions.get('window').width} // height will be calculated automatically
       source={{uri: '<image uri>'}}
   />
);

3
刚试图在2022年使用这个程序,但它已经有两年没有更新了,这意味着依赖关系无法解决最新的React 17x。 - Marc

6

我创建了一个钩子来计算图像的宽高比:

function useImageAspectRatio(imageUrl) {
  const [aspectRatio, setAspectRatio] = useState(1);

  useEffect(() => {
    if (!imageUrl) {
      return;
    }

    let isValid = true;
    Image.getSize(imageUrl, (width, height) => {
      if (isValid) {
        setAspectRatio(width / height);
      }
    });

    return () => {
      isValid = false;
    };
  }, [imageUrl]);

  return aspectRatio;
}

使用这个方法,您可以仅设置宽度或高度的一个值,并自动计算另一个值:

function App() {
  const aspectRatio = useImageAspectRatio(imageUrl);

  return (
    <Image 
      src={{ uri: imageUrl }}
      style={{ width: 200, aspectRatio }}
    />
  )
}

6

这是 @TheJizel 的钩子版本的答案。我知道图片的宽度,但想要图片的高度,所以下面的代码适用于我:

    const ScaledImage = props => {

    const [width, setWidth] = useState()
    const [height, setHeight] = useState()
    const [imageLoading, setImageLoading] = useState(true)

    useEffect(() => {
        Image.getSize(props.uri, (width1, height1) => {
            if (props.width && !props.height) {
                setWidth(props.width)
                setHeight(height1 * (props.width / width1))
            } else if (!props.width && props.height) {
                setWidth(width1 * (props.height / height1))
                setHeight(props.height)
            } else {
                setWidth(width1)
                setHeight(height1)
            }
            setImageLoading(false)
        }, (error) => {
            console.log("ScaledImage,Image.getSize failed with error: ", error)
        })
    }, [])


    return (
        height ?
            <View style={{ height: height, width: width, borderRadius: 5, backgroundColor: "lightgray" }}>
                <Image
                    source={{ uri: props.uri }}
                    style={{ height: height, width: width, borderRadius: 5, }}
                />
            </View>
            : imageLoading ?
                <ActivityIndicator size="large" />
                : null
    );
}

用法:

<ScaledImage width={Dimensions.get('window').width * 0.8} uri={imageurl} />

5

TypeScript 版本的 @TheJizel 答案,包含可选的 style 属性和 failure 回调函数,在 Image.getSize 中使用:

import * as React from 'react'
import {Image} from 'react-native'

interface Props {
    uri: string
    width?: number
    height?: number
    style?
}

interface State {
    source: {}
    width: number
    height: number
}

export default class ScaledImage extends React.Component<Props, State> {
    constructor(props) {
        super(props)
        this.state = {
            source: {uri: this.props.uri},
            width: 0,
            height: 0,
        }
    }

    componentWillMount() {
        Image.getSize(this.props.uri, (width, height) => {
            if (this.props.width && !this.props.height) {
                this.setState({width: this.props.width, height: height * (this.props.width / width)})
            } else if (!this.props.width && this.props.height) {
                this.setState({width: width * (this.props.height / height), height: this.props.height})
            } else {
                this.setState({width: width, height: height})
            }
        }, (error) => {
            console.log("ScaledImage:componentWillMount:Image.getSize failed with error: ", error)
        })
    }

    render() {
        return <Image source={this.state.source} style={[this.props.style, {height: this.state.height, width: this.state.width}]}/>
    }
}

示例用法:

<ScaledImage style={styles.scaledImage} uri={this.props.article.coverImageUrl} width={Dimensions.get('window').width}/>

3

首先尝试这个方法,看看是否可以解决你的问题:https://github.com/facebook/react-native/commit/5850165795c54b8d5de7bef9f69f6fe6b1b4763d

如果这不起作用,那么你可以实现自己的图像组件。但是,你需要重写onLayout方法来获取所需的宽度以便计算高度,而不是将宽度作为属性传递。如果你不知道宽度并且希望RN为你完成布局,则此方法更好。缺点是onLayout会在一次布局和渲染之后才被调用,因此你可能会注意到组件会稍微移动一下。


最终,实现自己的组件是最好的解决方案。 - ValRob

2

根据上面的答案,我使用TypeScript创建了一个功能组件,只下载一次图像(因为第二次会被缓存:https://reactnative.dev/docs/image#getsize),如果只传递一个值; 并且根据传递的属性计算高度和宽度。

    import { useFocusEffect } from '@react-navigation/native';
    import React from 'react';
    import { ImageProps, ImageURISource } from 'react-native';
    import { useIsMounted } from '../../hooks/is-mounted';
    import { DrImageStyl } from './styled';
    import { getImageSizes } from '../../utils/util';
    
    interface DrSource extends ImageURISource {
      uri: string;
    }
    
    interface DrImageProps extends ImageProps {
      source: DrSource;
      width?: number;
      height?: number;
    }
    
    const DrImage: React.FC<DrImageProps> = ({
      width: widthProp,
      height: heightProp,
      source,
      ...rest
    }: DrImageProps) => {
      const isMountedRef = useIsMounted();
    
      const [sizes, setSizes] = React.useState({
        width: widthProp,
        height: heightProp,
      });
    
      useFocusEffect(
        React.useCallback(() => {
          const getImageSizesState = async () => {
            try {
              const { width, height } = await getImageSizes({
                uri: source.uri,
                width: widthProp,
                height: heightProp,
              });
    
              if (isMountedRef.current) {
                setSizes({ width, height });
              }
            } catch (error) {
              console.log('Erro em dr-image getImageSizesState:', error);
            }
          };
    
          getImageSizesState();
        }, [widthProp, heightProp, source.uri])
      );
    
      return (
        <>
          {!!sizes.height && !!sizes.width && (
            <DrImageStyl sizes={sizes} source={source} {...rest} />
          )}
        </>
      );
    };

export default DrImage;

我使用了一个钩子来确定在异步函数执行后,组件是否仍然挂载(useIsMounted):

import React from 'react';

export const useIsMounted = (): React.MutableRefObject<boolean> => {
  const isMountedRef = React.useRef(false);
  React.useEffect(() => {
    isMountedRef.current = true;
    return () => {
      isMountedRef.current = false;
    };
  }, []);
  return isMountedRef;
};

我使用了styled-components模块来制作组件的CSS(DrImageStyl):
import React from 'react';
import styled, { css } from 'styled-components/native';

interface Sizes {
  width?: number;
  height?: number;
}

interface DrImageStylProps {
  sizes: Sizes;
}

export const DrImageStyl = styled.Image<DrImageStylProps>`
  ${({ sizes }) => {
    const { width, height } = sizes;

    return css`
      ${width ? `width: ${width}px;` : ''}
      ${height ? `height: ${height}px;` : ''}
    `;
  }}
`;

我将计算其他图片尺寸的代码(getImageSizes)单独分离出来:

import { Image } from 'react-native';

interface GetImageSizesParams {
  uri: string;
  height?: number;
  width?: number;
}

export function getImageSizes({
  height: heightParam,
  width: widthParam,
  uri,
}: GetImageSizesParams): Promise<{
  width: number;
  height: number;
}> {
  return new Promise((resolve, reject) => {
    function onSuccess(width: number, height: number) {
      let widthResolve: number | undefined;
      let heightResolve: number | undefined;

      if (widthParam && !heightParam) {
        widthResolve = widthParam;
        heightResolve = height * (widthParam / width);
      } else if (!widthParam && heightParam) {
        widthResolve = width * (heightParam / height);
        heightResolve = heightParam;
      } else {
        widthResolve = widthParam;
        heightResolve = heightParam;
      }

      resolve({
        width: widthResolve as number,
        height: heightResolve as number,
      });
    }

    function onError(error: any) {
      reject(error);
    }
    try {
      Image.getSize(uri, onSuccess, onError);
    } catch (error) {
      console.log('error', error);
    }
  });
}

欢迎来到Stack Overflow。请强调您的方法在现有答案中的显著之处。 - greybeard

2

不需要使用任何库来实现此功能,而是使用以下解决方案:

import React from  'react';
import { ImageProps } from 'react-native';
import FastImage from "react-native-fast-image";

const AutoHeightImage = React.memo(function AutoHeightImage ({ width,imageStyle, ...props }: ImageProps) {
  const [state, setstate] = React.useState(0)
  return (
    <FastImage 
      {...props}
      style={[{ width: width, height: state }, imageStyle]}
      resizeMode={FastImage.resizeMode.contain}
      onLoad={(evt) => {
        setstate((evt.nativeEvent.height / evt.nativeEvent.width) * width)
      }}
    />
  )
})

export default AutoHeightImage;

如何使用上述自定义组件:
 <AutoHeightImage
     width={(Dimensions.get('window').width)}
     source={{ uri: 'image url' }}/>

React Native Fast Image是从https://github.com/DylanVann/react-native-fast-image引用的快速图像库。


1
react-native-fast-image 是一个库吗? - owenmelbz
@owenmelbz 是的,它是一个库。 - Lokesh Desai
2
我明白了,抱歉误解了你的意思:不需要使用任何库来实现这个,而是使用以下解决方案: - owenmelbz
是的,@owenmelbz,你可以使用<Image而不是FastImage。我只是举了一个例子,展示了我如何通过自定义类来实现这一点。 - Lokesh Desai

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接