【问题标题】:Auto scale image height with React Native使用 React Native 自动缩放图像高度
【发布时间】:2017-06-29 10:27:36
【问题描述】:

在我的 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???<<<
  }
)

【问题讨论】:

标签: react-native


【解决方案1】:

试试这个:

 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 一个。

【讨论】:

  • 这行得通,非常感谢。令我惊讶的是,没有内置的方法可以实现这一点。我猜 RN 还是很新的。
  • @plmok61 您也可以尝试Image 类中的resizeMode 属性,并在您的样式中应用flex。我最初尝试过这个,但我不喜欢容器的高度不会根据图像重新缩放而扩大。
  • 要添加图片url的实际路径吗?
  • @Somename 将其传递给 uri 属性。
  • 抱歉,我是 RN 新手。还是无法理解。多次阅读文档。请解释实际的网络网址在哪里。非常感谢。
【解决方案2】:

有一个属性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>
    )
}

【讨论】:

  • 此解决方案不适用于“expo”:“^32.0.0”、“react”:“16.5.0”、“react-native”:“github.com/expo/react-native/archive/sdk-32.0.0.tar.gz”,仅设置高度有效,但仅在样式中设置宽度会给出空白图像。
  • 它确实会自动缩放图像以适应定义的大小,这可以通过一些想法帮助解决上述问题 - 当然帮助我处理未知尺寸的图像。
  • 似乎不适用于最新版本
  • @Shivam,我今天检查过了,它工作正常。我用其他解决问题的方法的样本更新了我的答案。希望对您有所帮助!
  • @Underdog 你的“expo”和“react”版本无关紧要。至于“react-native”,我今天检查了一下,resizeMode 属性没有被弃用,也没有迹象表明他们打算这样做。
【解决方案3】:

看看这个库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>'}}
   />
);

【讨论】:

    【解决方案4】:

    TypeScript 版本的 @TheJizel 答案,带有可选的 style 属性和 Image.getSize 中的 failure 回调:

    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}/>
    

    【讨论】:

      【解决方案5】:

      @TheJizel 答案的 Hooks 版本。我知道宽度但想要图像的高度,所以下面对我有用:

          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} />
      

      【讨论】:

        【解决方案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 }}
            />
          )
        }
        

        【讨论】:

          【解决方案7】:

          首先试试这个,看看它是否适合你:https://github.com/facebook/react-native/commit/5850165795c54b8d5de7bef9f69f6fe6b1b4763d

          如果没有,那么您可以实现自己的图像组件。但不是将宽度作为道具,而是覆盖onLayout 方法,该方法为您提供所需的宽度,以便您可以计算高度。如果您不知道宽度并希望 RN 为您做布局,这会更好。缺点是onLayout 在布局和渲染一次之后被调用。所以你可能会注意到你的组件在移动。

          【讨论】:

          • 毕竟……实现自己的组件是最好的解决方案。
          【解决方案8】:

          根据上面的答案,我用 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);
              }
            });
          }
          

          【讨论】:

          • 欢迎来到堆栈溢出。鉴于已有的答案,请强调是什么让您的方法引人注目。
          【解决方案9】:

          这里有一个非常简单的解决方案的要点,该解决方案利用@Haitao Li 的建议使用 aspectRatio:

          https://gist.github.com/tpraxl/02dc4bfcfa301340d26a0bf2140cd8b9

          不需要魔法,也不需要计算。如果您知道原始图像的尺寸,则纯“CSS”。

          【讨论】:

            【解决方案10】:

            建议的解决方案有效,但您必须下载图像两次,一次确定大小,另一次实际显示图像,这是一种不同的方法,图像最初加载平方并调整大小。

            import React, { Component, } from "react";
            import { Image } from "react-native";
            import PropTypes from 'prop-types'
            
                export default class ScaledImage extends Component {
                    state = {}
            
                    componentWillMount() {
                        const { uri, width, height } = this.props;
                        this.setState({ source: { uri }, width: width || height, height: height || width });
                    }
            
                    render() {
                        return (
                            <Image
                                source={this.state.source}
                                onLoad={(value) => {
                                    const { height, width } = value.nativeEvent.source;
                                    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 });
                                    }
            
                                }}
                                style={{ height: this.state.height, width: this.state.width }}
                            />
                        );
                    }
                }
            
                ScaledImage.propTypes = {
                    uri: PropTypes.string.isRequired,
                    width: PropTypes.number,
                    height: PropTypes.number
                };
            

            【讨论】:

              【解决方案11】:

              这个在世博会上为我工作

              <Image style={{flex:1,width:null,height:null }} resizeMode={'contain'}  source={{uri: 'http://134.209.40.60/meApunto/1567655610795_1944474896.png'}}></Image>
              

              https://forums.expo.io/t/how-to-fit-a-big-image-into-a-fixed-container-without-resizemode-help/27639

              【讨论】:

              • @miteshkalal 我认为这是特定于 expo 的,如果您不熟悉 react native,请看一下 flutter,这将是未来。
              • 不,我不是 react-native 的新手。未来 react-native 会发生什么。
              • 使用 undefined 代替 null
              【解决方案12】:

              基于@TheJizel 的想法,我使用 aspectRatio 样式属性制作了一些东西。以下类在设置宽度但省略高度时有效。这也适用于百分比作为宽度。

              import React from "react";
              import { Image } from "react-native";
              
              export default class ScaledImage extends React.Component {
              
                state = {
                  aspectRatio: 0
                }
              
                setAspectRatio(ratio) {
                  this.setState({
                    aspectRatio: ratio
                  });
                }
              
                componentWillMount() {
                  if (Array.isArray(this.props.source)) {
                    console.warn("ScaledImage received an array as source instead of local file resource or ImageURISource.")
                  } else if(typeof this.props.source === "number") {
                    // Resolve local file resource
                    const resolved = Image.resolveAssetSource(this.props.source);
              
                    // We assume 100% width, so we set the aspect ratio we want for it's height
                    this.setAspectRatio(resolved.width / resolved.height);
              
                  } else if (this.props.source.uri) {
                    // Resolve remote resource
                    Image.getSize(this.props.source.uri, (width, height) => {
                       this.setAspectRatio( width / height);
                    }, (err) => {
                      console.error(err);
                    });
              
                  } else {
                    console.warn("ScaledImage did not receive a valid source uri.");
                  }
                }
              
                render() {
                  if(!this.state.aspectRatio) return null;
              
                  const props = {
                    ...this.props,
                    style: [this.props.style, {
                      aspectRatio: this.state.aspectRatio
                    }]
                  };
              
                  return (
                    <Image {...props} />
                  )
                }
              }
              

              用法:

              <ScaledImage source={{ uri: "<URI HERE>" }} style={{ width: "100%" }} />
              

              【讨论】:

                【解决方案13】:

                你有 3 个号码:

                1. 图片宽度
                2. 图片高度
                3. 屏幕宽度

                你应该把“屏幕宽度”放在宽度样式中并计算 样式设置的高度??!!

                componentWillMount() {
                
                    Image.getSize(this.props.product.image, (width, height) => {
                
                        const screenWidth = Math.round(Dimensions.get('window').width);  
                        this.setState({screenWidth:screenWidth});
                        Calculatedheight = screenWidth * height / width ;
                        this.setState({Calculatedheight : Calculatedheight });
                
                    });
                
                }
                

                <Image
                  source={{uri: product.image,cache: 'only-if-cached'}}
                  style={{ height: this.state.screenHeight , width: this.state.Calculatedheight }}
                
                />
                

                【讨论】:

                  【解决方案14】:

                  这是我在生产中使用的一些代码。后端用户可以制作任何大小和纵横比的徽标图像,但我需要徽标以适合最大宽度的精确高度。结果是我的自缩放组件:

                  import React, { useState, useLayoutEffect, SFC } from "react";
                  import { Image } from "react-native";
                  import { Spinner } from "native-base";
                  
                  
                  interface INetworkImage {
                      targetHeight: number,
                      uri: string,
                      maxWidth: number
                  }
                  
                  const NetworkImage: SFC<INetworkImage> = ({ uri, targetHeight, maxWidth }) => {
                  
                      useLayoutEffect(() => setNaturalDimensions(uri), []);
                  
                      const [imageWidth, setWidth] = useState(0);
                      const [imageHeight, setHeight] = useState(0);
                      const [scaleFactor, setScale] = useState(1);
                  
                      function setNaturalDimensions(uri: string) {
                          Image.getSize(uri, (width: number, height: number) => {
                              if (width > maxWidth) {
                                  // too wide case
                                  setScale(maxWidth / width);
                              } else {
                                  // scale to height case
                                  setScale(targetHeight / height);
                              }
                              setWidth(width);
                              setHeight(height);
                          }, (error: any) => {
                              console.log("error", error);
                          });
                      }
                      function adjustView(e) {
                          if (e.nativeEvent.layout.width > maxWidth) {
                              setScale(scaleFactor * (maxWidth/e.nativeEvent.layout.width));
                          }
                      }
                      return (
                          imageHeight ?
                          <Image
                              onLayout={(e) => adjustView(e)}
                              source={{ uri: uri }}
                              style={{
                                  width: imageWidth * scaleFactor,
                                  height: imageHeight * scaleFactor,
                                  resizeMode: "contain",
                              }}
                          />:
                          <Spinner color='#454c7a' />
                          );
                  }
                  export default NetworkImage;
                  

                  然后我通过将 uri、targetHeight 和 maxwidth 作为 props 传递来使用它:

                  export const deviceWidth = Dimensions.get("window").width;
                  
                  <NetworkImage
                      uri={"https://purdyPic.com/image1"}
                      targetHeight={300}
                      maxWidth={deviceWidth * 0.85}
                                            />
                  

                  【讨论】:

                    【解决方案15】:

                    一个解决方案

                    <Image source={...} style={{ transform: [{ scale: 0.5 }] }} />
                    

                    【讨论】:

                    • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
                    【解决方案16】:

                    所以这一切都帮了我很多忙?

                    我的特定场景涉及从服务器获取可能是纵向或横向的图像,我需要将它们放入&lt;View&gt;

                    这意味着“已知”尺寸属于该视图,我通过onLayout 获得(简化代码仅显示设置“高度”的示例):

                    <View onLayout={(event) => setCellHeight(event.nativeEvent.layout.height)}>
                    

                    现在使用我已知的 displayAreaHeightdisplayAreaWidth 值,我需要调整图像大小:

                      // Set image size for portrait/landscape scenarios, reducing the total image size when
                      // an overflow of the display area would occur.
                    
                      if (image.height > image.width) { // Portrait Image
                        const ratio = displayAreaHeight / image.height;
                        imageHeight = displayAreaHeight;
                        imageWidth = image.width * ratio;
                        if (imageWidth > displayAreaWidth) {
                          const heightReductionRatio = displayAreaWidth / imageWidth;
                          imageHeight *= heightReductionRatio;
                          imageWidth = displayAreaWidth;
                        }
                      } else {
                        const ratio = displayAreaWidth / image.width;
                        imageHeight = image.height * ratio;
                        imageWidth = displayAreaWidth;
                        if (imageHeight > displayAreaHeight) {
                          const widthReductionRatio = displayAreaHeight / imageHeight;
                          imageWidth *= widthReductionRatio;
                          imageHeight = displayAreaHeight;
                        }
                      }
                    

                    希望这个,连同这里的所有其他很好的回应,可以帮助别人?

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2015-06-22
                      • 1970-01-01
                      • 2012-08-30
                      • 1970-01-01
                      • 2017-01-22
                      • 2013-07-03
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多