callmetwan
callmetwan

Reputation: 1350

Scale image down to device width in React Native ScrollView

I have an image I'd like to be full width of the device screen.

Using just a View I can achieve this:

<View style={{flex:1}}>
     <Image resizeMode="contain" style={{flex: 1, height: undefined, width: undefined}} source={this.props.images.infoImage} />
</View>

Changing this to a ScrollView causes the image to stop rendering completely:

<ScrollView style={{flex:1}}>
     <Image resizeMode="contain" style={{flex: 1, height: undefined, width: undefined}} source={this.props.images.infoImage} />
</ScrollView>

I found that if I set a height on the image it would render...but I want the image to dynamically respond to the size of the device.

How can I solve this without declaring an explicit height?

Upvotes: 0

Views: 4178

Answers (3)

callmetwan
callmetwan

Reputation: 1350

As per @martinarroyo's comment I added contentContainerStyle={{flex:1}} to the ScrollView, like so:

<ScrollView contentContainerStyle={{flex:1}}>
    <Image resizeMode="contain" style={{flex: 1, width: undefined, height: undefined}} source={this.props.images.infoImage} />
</ScrollView

This completely resolved my issue. The image is the full width of the display while maintaining the correct aspect ratio.

EDIT: Turns out the image frame height stays the same but the image scales down. This means it has large chunks of padding on the top and bottom. As of now I don't know how to remove it.

EDIT 2:

It seems there aren't any tools out of the box with RN and ultimately ended up using a modified version of @Ihor Burlachenko's solution. I created a custom component that takes desired width/height constraints and scales it based on that. I need to use this for local files, which Image.getSize() method does not work on, so I modified Ihor's solution to allow for that by passing in a dimensions object that contain width and height.

import React from 'react';
import { Image } from 'react-native';

export default class ScalableImage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            width: null,
            height: null,
        }
    }

    componentDidMount() {
        if(typeof this.props.source === 'string') {
            Image.getSize(this.props.source, this._calculateImageDimensions, console.log);
        } else if(this.props.dimensions) {
            this._calculateImageDimensions(this.props.dimensions.width, this.props.dimensions.height)
        }
    }

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

    _calculateImageDimensions(width, height) {
        let ratio;

        if (this.props.width && this.props.height) {
            ratio = Math.min(this.props.width / width, this.props.height / height);
        }
        else if (this.props.width) {
            ratio = this.props.width / width;
        }
        else if (this.props.height) {
            ratio = this.props.height / height;
        }

        this.setState({ width: width * ratio, height: height * ratio });
    }
}

ScalableImage.propTypes = {
    width: React.PropTypes.number,
    height: React.PropTypes.number,
    dimensions: React.PropTypes.object
};

I then use it as so:

<ScalableImage source={require('../path/to/local/image.png')} 
               width={Dimensions.get('window').width()} 
               dimensions={{width: 700, height: 400}} />

Upvotes: 6

Ihor Burlachenko
Ihor Burlachenko

Reputation: 4905

I ended up wrapping React Image inside custom Image component which calculates height dynamically to keep the image ratio in onComponentDidMount:

Image.getSize(this.props.source.uri, (width, height) => {
    let ratio;

    if (this.props.width && this.props.height) {
        ratio = Math.min(this.props.width / width, this.props.height / height);
    }
    else if (this.props.width) {
        ratio = this.props.width / width;
    }
    else if (this.props.height) {
        ratio = this.props.height / height;
    }

    this.setState({ width: width * ratio, height: height * ratio });
}, console.log);

And then I use it like that:

<Image width={Dimensions.get('window').width} source={{uri: '<image uri>'}} />

Upvotes: 0

martinarroyo
martinarroyo

Reputation: 9701

As a last resource, you can use Dimensions.

Get the windows's width and height on every render, that way you'll account for changes in size (basically, rotations) and will match every device.

You can use it like this:

import {Dimensions} from 'react-native'+
// Then, in your component:
render(){
    const {width, height} = Dimensions.get('window'):
    return (<ScrollView style={{flex:1}}>
     <Image resizeMode="contain" style={{flex: 1, height, width}} source={this.props.images.infoImage} />
</ScrollView>)
}

Upvotes: 0

Related Questions