mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
d3d92b9a1d
Summary: The Image component in Android now supports the same cache control behaviour as in iOS, the examples needed to be separated in the past because the support was not the same for iOS and Android, but now that it is, we can unify the example. ## Changelog: [INTERNAL] - Unify RNTester Cache Policy Image example Pull Request resolved: https://github.com/facebook/react-native/pull/51580 Test Plan: <details> <summary>RNTester Screenshots</summary> | iOS | Android | |--------|-------| |  |  | </details> Reviewed By: yungsters Differential Revision: D75538812 Pulled By: Abbondanzo fbshipit-source-id: 7705c8f824cb18cebc39e84b6f48979035dc104c
1740 lines
48 KiB
JavaScript
1740 lines
48 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
|
import type {ImageProps, LayoutChangeEvent} from 'react-native';
|
|
|
|
import RNTesterButton from '../../components/RNTesterButton';
|
|
import RNTesterText from '../../components/RNTesterText';
|
|
import ImageCapInsetsExample from './ImageCapInsetsExample';
|
|
import React from 'react';
|
|
import {useEffect, useState} from 'react';
|
|
import {Image, ImageBackground, StyleSheet, Text, View} from 'react-native';
|
|
|
|
const IMAGE1 =
|
|
'https://www.facebook.com/assets/fb_lite_messaging/E2EE-settings@3x.png';
|
|
const IMAGE2 =
|
|
'https://www.facebook.com/ar_effect/external_textures/648609739826677.png';
|
|
|
|
const base64Icon =
|
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
|
|
const IMAGE_PREFETCH_URL = `${IMAGE1}?r=1&t=${Date.now()}`;
|
|
const prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
|
|
|
|
type ImageSource = $ReadOnly<{
|
|
uri: string,
|
|
}>;
|
|
|
|
type BlobImageProps = $ReadOnly<{
|
|
url: string,
|
|
}>;
|
|
|
|
const BlobImage = ({url}: BlobImageProps): React.Node => {
|
|
const [objectURL, setObjectURL] = useState<?string>(null);
|
|
|
|
useEffect(() => {
|
|
// $FlowFixMe[unused-promise]
|
|
(async () => {
|
|
const result = await fetch(url);
|
|
const blob = await result.blob();
|
|
setObjectURL(URL.createObjectURL(blob));
|
|
})();
|
|
}, [url]);
|
|
|
|
return objectURL !== null ? (
|
|
<Image source={{uri: objectURL}} style={styles.base} />
|
|
) : (
|
|
<Text>Object URL not created yet</Text>
|
|
);
|
|
};
|
|
|
|
type BlobImageExampleState = {};
|
|
|
|
type BlobImageExampleProps = $ReadOnly<{
|
|
urls: string[],
|
|
}>;
|
|
|
|
class BlobImageExample extends React.Component<
|
|
BlobImageExampleProps,
|
|
BlobImageExampleState,
|
|
> {
|
|
render(): React.Node {
|
|
return (
|
|
<View style={styles.horizontal}>
|
|
{this.props.urls.map(url => (
|
|
<BlobImage key={url} url={url} />
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
type NetworkImageCallbackExampleProps = $ReadOnly<{
|
|
source: ImageSource,
|
|
prefetchedSource: ImageSource,
|
|
}>;
|
|
|
|
const NetworkImageCallbackExample = ({
|
|
source,
|
|
prefetchedSource,
|
|
}: NetworkImageCallbackExampleProps): React.Node => {
|
|
const [events, setEvents] = useState<$ReadOnlyArray<string>>([]);
|
|
const [startLoadPrefetched, setStartLoadPrefetched] = useState(false);
|
|
const [mountTime, setMountTime] = useState(Date.now());
|
|
|
|
useEffect(() => {
|
|
setMountTime(Date.now());
|
|
}, []);
|
|
|
|
const _loadEventFired = (event: string) => {
|
|
setEvents(state => [...state, event]);
|
|
};
|
|
|
|
return (
|
|
<View>
|
|
<Image
|
|
source={source}
|
|
style={[styles.base, styles.visibleOverflow]}
|
|
onError={event => {
|
|
_loadEventFired(
|
|
`✘ onError "${event.nativeEvent.error}" (+${Date.now() - mountTime}ms)`,
|
|
);
|
|
}}
|
|
onLoadStart={() =>
|
|
_loadEventFired(`✔ onLoadStart (+${Date.now() - mountTime}ms)`)
|
|
}
|
|
onProgress={event => {
|
|
const {loaded, total} = event.nativeEvent;
|
|
const percent = Math.round((loaded / total) * 100);
|
|
_loadEventFired(
|
|
`✔ onProgress ${percent}% (+${Date.now() - mountTime}ms)`,
|
|
);
|
|
}}
|
|
onLoad={event => {
|
|
if (event.nativeEvent.source) {
|
|
const url = event.nativeEvent.source.uri;
|
|
_loadEventFired(
|
|
`✔ onLoad (+${Date.now() - mountTime}ms) for URL ${url}`,
|
|
);
|
|
} else {
|
|
_loadEventFired(`✔ onLoad (+${Date.now() - mountTime}ms)`);
|
|
}
|
|
}}
|
|
onLoadEnd={() => {
|
|
_loadEventFired(`✔ onLoadEnd (+${Date.now() - mountTime}ms)`);
|
|
setStartLoadPrefetched(true);
|
|
prefetchTask.then(
|
|
() => {
|
|
_loadEventFired(`✔ prefetch OK (+${Date.now() - mountTime}ms)`);
|
|
// $FlowFixMe[unused-promise]
|
|
Image.queryCache([IMAGE_PREFETCH_URL]).then(map => {
|
|
const result = map[IMAGE_PREFETCH_URL];
|
|
if (result) {
|
|
_loadEventFired(
|
|
`✔ queryCache "${result}" (+${Date.now() - mountTime}ms)`,
|
|
);
|
|
} else {
|
|
_loadEventFired(
|
|
`✘ queryCache (+${Date.now() - mountTime}ms)`,
|
|
);
|
|
}
|
|
});
|
|
},
|
|
error => {
|
|
_loadEventFired(
|
|
`✘ prefetch failed (+${Date.now() - mountTime}ms)`,
|
|
);
|
|
},
|
|
);
|
|
}}
|
|
/>
|
|
{startLoadPrefetched && (
|
|
<Image
|
|
source={prefetchedSource}
|
|
style={[styles.base, styles.visibleOverflow]}
|
|
onLoadStart={() =>
|
|
_loadEventFired(
|
|
`✔ (prefetched) onLoadStart (+${Date.now() - mountTime}ms)`,
|
|
)
|
|
}
|
|
onLoad={event => {
|
|
if (event.nativeEvent.source) {
|
|
const url = event.nativeEvent.source.uri;
|
|
_loadEventFired(
|
|
`✔ (prefetched) onLoad (+${
|
|
Date.now() - mountTime
|
|
}ms) for URL ${url}`,
|
|
);
|
|
} else {
|
|
_loadEventFired(
|
|
`✔ (prefetched) onLoad (+${Date.now() - mountTime}ms)`,
|
|
);
|
|
}
|
|
}}
|
|
onLoadEnd={() =>
|
|
_loadEventFired(
|
|
`✔ (prefetched) onLoadEnd (+${Date.now() - mountTime}ms)`,
|
|
)
|
|
}
|
|
/>
|
|
)}
|
|
<RNTesterText style={styles.networkImageText}>
|
|
{events.join('\n')}
|
|
</RNTesterText>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
type NetworkImageExampleState = {
|
|
error: ?string,
|
|
loading: boolean,
|
|
progress: $ReadOnlyArray<number>,
|
|
};
|
|
|
|
class NetworkImageExample extends React.Component<
|
|
ImageProps,
|
|
NetworkImageExampleState,
|
|
> {
|
|
state: NetworkImageExampleState = {
|
|
error: null,
|
|
loading: false,
|
|
progress: [],
|
|
};
|
|
|
|
render(): React.Node {
|
|
return this.state.error != null ? (
|
|
<RNTesterText variant="label">{this.state.error}</RNTesterText>
|
|
) : (
|
|
<>
|
|
<Image
|
|
{...this.props}
|
|
style={[styles.base, styles.visibleOverflow]}
|
|
onLoadStart={e => this.setState({loading: true})}
|
|
onError={e =>
|
|
this.setState({error: e.nativeEvent.error, loading: false})
|
|
}
|
|
onProgress={e => {
|
|
const {loaded, total} = e.nativeEvent;
|
|
this.setState(prevState => ({
|
|
progress: [
|
|
...prevState.progress,
|
|
Math.round((100 * loaded) / total),
|
|
],
|
|
}));
|
|
}}
|
|
onLoad={() => this.setState({loading: false, error: null})}
|
|
/>
|
|
<RNTesterText variant="label">
|
|
Progress:{' '}
|
|
{this.state.progress.map(progress => `${progress}%`).join('\n')}
|
|
</RNTesterText>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
type ImageSizeExampleState = {
|
|
width: number,
|
|
height: number,
|
|
};
|
|
|
|
type ImageSizeExampleProps = $ReadOnly<{
|
|
source: ImageSource,
|
|
}>;
|
|
|
|
class ImageSizeExample extends React.Component<
|
|
ImageSizeExampleProps,
|
|
ImageSizeExampleState,
|
|
> {
|
|
state: ImageSizeExampleState = {
|
|
width: 0,
|
|
height: 0,
|
|
};
|
|
|
|
componentDidMount() {
|
|
Image.getSize(this.props.source.uri, (width, height) => {
|
|
this.setState({width, height});
|
|
});
|
|
}
|
|
|
|
render(): React.Node {
|
|
return (
|
|
<View style={styles.flexRow}>
|
|
<Image style={styles.imageSizeExample} source={this.props.source} />
|
|
<RNTesterText>
|
|
Actual dimensions:{'\n'}
|
|
Width: {this.state.width}, Height: {this.state.height}
|
|
</RNTesterText>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
type MultipleSourcesExampleState = {
|
|
width: number,
|
|
height: number,
|
|
};
|
|
|
|
type MultipleSourcesExampleProps = $ReadOnly<{}>;
|
|
|
|
class MultipleSourcesExample extends React.Component<
|
|
MultipleSourcesExampleProps,
|
|
MultipleSourcesExampleState,
|
|
> {
|
|
state: MultipleSourcesExampleState = {
|
|
width: 30,
|
|
height: 30,
|
|
};
|
|
|
|
increaseImageSize = () => {
|
|
if (this.state.width >= 100) {
|
|
return;
|
|
}
|
|
this.setState({
|
|
width: this.state.width + 10,
|
|
height: this.state.height + 10,
|
|
});
|
|
};
|
|
|
|
decreaseImageSize = () => {
|
|
if (this.state.width <= 10) {
|
|
return;
|
|
}
|
|
this.setState({
|
|
width: this.state.width - 10,
|
|
height: this.state.height - 10,
|
|
});
|
|
};
|
|
|
|
render(): React.Node {
|
|
return (
|
|
<View>
|
|
<View style={styles.spaceBetweenView}>
|
|
<RNTesterText
|
|
style={styles.touchableText}
|
|
onPress={this.decreaseImageSize}>
|
|
Decrease image size
|
|
</RNTesterText>
|
|
<RNTesterText
|
|
style={styles.touchableText}
|
|
onPress={this.increaseImageSize}>
|
|
Increase image size
|
|
</RNTesterText>
|
|
</View>
|
|
<RNTesterText>
|
|
Container image size: {this.state.width}x{this.state.height}{' '}
|
|
</RNTesterText>
|
|
<View style={{height: this.state.height, width: this.state.width}}>
|
|
<Image
|
|
style={styles.flex}
|
|
source={[
|
|
{
|
|
uri: IMAGE1,
|
|
width: 38,
|
|
height: 38,
|
|
},
|
|
{
|
|
uri: IMAGE2,
|
|
width: 100,
|
|
height: 100,
|
|
},
|
|
]}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
type LoadingIndicatorSourceExampleState = {
|
|
imageHash: number,
|
|
};
|
|
|
|
type LoadingIndicatorSourceExampleProps = $ReadOnly<{}>;
|
|
|
|
class LoadingIndicatorSourceExample extends React.Component<
|
|
LoadingIndicatorSourceExampleProps,
|
|
LoadingIndicatorSourceExampleState,
|
|
> {
|
|
state: LoadingIndicatorSourceExampleState = {
|
|
imageHash: Date.now(),
|
|
};
|
|
|
|
reloadImage = () => {
|
|
this.setState({
|
|
imageHash: Date.now(),
|
|
});
|
|
};
|
|
|
|
loaderGif: {uri: string} = {
|
|
uri: 'https://media1.giphy.com/media/3oEjI6SIIHBdRxXI40/200.gif',
|
|
};
|
|
|
|
render(): React.Node {
|
|
const loadingImage = {
|
|
uri: `${IMAGE2}?hash=${this.state.imageHash}`,
|
|
};
|
|
|
|
return (
|
|
<View>
|
|
<View style={styles.spaceBetweenView}>
|
|
<RNTesterText style={styles.touchableText} onPress={this.reloadImage}>
|
|
Refresh Image
|
|
</RNTesterText>
|
|
</View>
|
|
<Image
|
|
loadingIndicatorSource={this.loaderGif}
|
|
source={loadingImage}
|
|
style={styles.base}
|
|
/>
|
|
<RNTesterText>Image Hash: {this.state.imageHash}</RNTesterText>
|
|
<RNTesterText>Image URI: {loadingImage.uri}</RNTesterText>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
type FadeDurationExampleState = {
|
|
imageHash: number,
|
|
};
|
|
|
|
type FadeDurationExampleProps = $ReadOnly<{}>;
|
|
|
|
class FadeDurationExample extends React.Component<
|
|
FadeDurationExampleProps,
|
|
FadeDurationExampleState,
|
|
> {
|
|
state: FadeDurationExampleState = {
|
|
imageHash: Date.now(),
|
|
};
|
|
|
|
reloadImage = () => {
|
|
this.setState({
|
|
imageHash: Date.now(),
|
|
});
|
|
};
|
|
|
|
render(): React.Node {
|
|
const loadingImage = {
|
|
uri: `${IMAGE2}?hash=${this.state.imageHash}`,
|
|
};
|
|
|
|
return (
|
|
<View>
|
|
<View style={styles.spaceBetweenView}>
|
|
<Text style={styles.touchableText} onPress={this.reloadImage}>
|
|
Refresh Image
|
|
</Text>
|
|
</View>
|
|
<Image fadeDuration={1500} source={loadingImage} style={styles.base} />
|
|
<RNTesterText>
|
|
This image will fade in over the time of 1.5s.
|
|
</RNTesterText>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
type OnLayoutExampleState = {
|
|
width: number,
|
|
height: number,
|
|
layoutHandlerMessage: string,
|
|
};
|
|
|
|
type OnLayoutExampleProps = $ReadOnly<{}>;
|
|
|
|
class OnLayoutExample extends React.Component<
|
|
OnLayoutExampleProps,
|
|
OnLayoutExampleState,
|
|
> {
|
|
state: OnLayoutExampleState = {
|
|
width: 30,
|
|
height: 30,
|
|
layoutHandlerMessage: 'No Message',
|
|
};
|
|
|
|
onLayoutHandler = (event: LayoutChangeEvent) => {
|
|
this.setState({
|
|
width: this.state.width,
|
|
height: this.state.height,
|
|
layoutHandlerMessage: JSON.stringify(event.nativeEvent),
|
|
});
|
|
console.log(event.nativeEvent);
|
|
};
|
|
|
|
increaseImageSize = () => {
|
|
if (this.state.width >= 100) {
|
|
return;
|
|
}
|
|
this.setState({
|
|
width: this.state.width + 10,
|
|
height: this.state.height + 10,
|
|
});
|
|
};
|
|
|
|
decreaseImageSize = () => {
|
|
if (this.state.width <= 10) {
|
|
return;
|
|
}
|
|
this.setState({
|
|
width: this.state.width - 10,
|
|
height: this.state.height - 10,
|
|
});
|
|
};
|
|
|
|
render(): React.Node {
|
|
return (
|
|
<View>
|
|
<RNTesterText>
|
|
Adjust the image size to trigger the OnLayout handler.
|
|
</RNTesterText>
|
|
<View style={styles.spaceBetweenView}>
|
|
<Text style={styles.touchableText} onPress={this.decreaseImageSize}>
|
|
Decrease image size
|
|
</Text>
|
|
<Text style={styles.touchableText} onPress={this.increaseImageSize}>
|
|
Increase image size
|
|
</Text>
|
|
</View>
|
|
<RNTesterText>
|
|
Container image size: {this.state.width}x{this.state.height}{' '}
|
|
</RNTesterText>
|
|
<View style={{height: this.state.height, width: this.state.width}}>
|
|
<Image
|
|
onLayout={this.onLayoutHandler}
|
|
style={styles.flex}
|
|
source={[
|
|
{
|
|
uri: IMAGE1,
|
|
width: 38,
|
|
height: 38,
|
|
},
|
|
{
|
|
uri: IMAGE1,
|
|
width: 76,
|
|
height: 76,
|
|
},
|
|
{
|
|
uri: IMAGE2,
|
|
width: 400,
|
|
height: 400,
|
|
},
|
|
]}
|
|
/>
|
|
</View>
|
|
<RNTesterText>
|
|
Layout Handler Message: {this.state.layoutHandlerMessage}
|
|
</RNTesterText>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
type OnPartialLoadExampleState = {
|
|
hasLoaded: boolean,
|
|
};
|
|
|
|
type OnPartialLoadExampleProps = $ReadOnly<{}>;
|
|
|
|
class OnPartialLoadExample extends React.Component<
|
|
OnPartialLoadExampleProps,
|
|
OnPartialLoadExampleState,
|
|
> {
|
|
state: OnPartialLoadExampleState = {
|
|
hasLoaded: false,
|
|
};
|
|
|
|
partialLoadHandler = () => {
|
|
this.setState({
|
|
hasLoaded: true,
|
|
});
|
|
};
|
|
|
|
render(): React.Node {
|
|
return (
|
|
<View>
|
|
<RNTesterText>
|
|
Partial Load Function Executed: {JSON.stringify(this.state.hasLoaded)}
|
|
</RNTesterText>
|
|
<Image
|
|
source={{
|
|
uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?&buster=${Math.random()}`,
|
|
}}
|
|
onPartialLoad={this.partialLoadHandler}
|
|
style={styles.base}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const VectorDrawableExample = () => {
|
|
return (
|
|
<View style={styles.flex} testID="vector-drawable-example">
|
|
<View style={styles.horizontal}>
|
|
<Image
|
|
source={require('../../assets/ic_android.xml')}
|
|
style={styles.vectorDrawable}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/ic_android.xml')}
|
|
style={styles.vectorDrawable}
|
|
tintColor="red"
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
function CacheControlExample(): React.Node {
|
|
const [reload, setReload] = useState(0);
|
|
|
|
const onReload = () => {
|
|
setReload(prevReload => prevReload + 1);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>Default</RNTesterText>
|
|
<Image
|
|
source={{
|
|
uri: fullImage.uri + '?cacheBust=default',
|
|
cache: 'default',
|
|
}}
|
|
style={styles.base}
|
|
key={reload}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>Reload</RNTesterText>
|
|
<Image
|
|
source={{
|
|
uri: fullImage.uri + '?cacheBust=reload',
|
|
cache: 'reload',
|
|
}}
|
|
style={styles.base}
|
|
key={reload}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>Force-cache</RNTesterText>
|
|
<Image
|
|
source={{
|
|
uri: fullImage.uri + '?cacheBust=force-cache',
|
|
cache: 'force-cache',
|
|
}}
|
|
style={styles.base}
|
|
key={reload}
|
|
onError={e => console.log(e.nativeEvent.error)}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Only-if-cached
|
|
</RNTesterText>
|
|
<Image
|
|
source={{
|
|
uri: fullImage.uri + '?cacheBust=only-if-cached',
|
|
cache: 'only-if-cached',
|
|
}}
|
|
style={styles.base}
|
|
key={reload}
|
|
onError={e => console.log(e.nativeEvent.error)}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.horizontal}>
|
|
<View style={styles.cachePolicyAndroidButtonContainer}>
|
|
<RNTesterButton onPress={onReload}>
|
|
Re-render image components
|
|
</RNTesterButton>
|
|
</View>
|
|
</View>
|
|
</>
|
|
);
|
|
}
|
|
|
|
const fullImage: ImageSource = {
|
|
uri: IMAGE2,
|
|
};
|
|
const smallImage = {
|
|
uri: IMAGE1,
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
base: {
|
|
width: 64,
|
|
height: 64,
|
|
margin: 4,
|
|
},
|
|
visibleOverflow: {
|
|
overflow: 'visible',
|
|
},
|
|
leftMargin: {
|
|
marginLeft: 10,
|
|
},
|
|
background: {
|
|
backgroundColor: '#222222',
|
|
},
|
|
sectionText: {
|
|
marginVertical: 6,
|
|
},
|
|
nestedText: {
|
|
marginLeft: 12,
|
|
marginTop: 20,
|
|
backgroundColor: 'transparent',
|
|
color: 'white',
|
|
},
|
|
resizeMode: {
|
|
width: 90,
|
|
height: 60,
|
|
borderWidth: 0.5,
|
|
borderColor: 'black',
|
|
},
|
|
resizeModeText: {
|
|
fontSize: 11,
|
|
marginBottom: 3,
|
|
},
|
|
icon: {
|
|
width: 15,
|
|
height: 15,
|
|
margin: 4,
|
|
},
|
|
horizontal: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
},
|
|
gif: {
|
|
flex: 1,
|
|
height: 200,
|
|
},
|
|
base64: {
|
|
flex: 1,
|
|
height: 50,
|
|
resizeMode: 'contain',
|
|
},
|
|
touchableText: {
|
|
fontWeight: '500',
|
|
color: 'blue',
|
|
},
|
|
networkImageText: {
|
|
marginTop: 20,
|
|
},
|
|
flex: {
|
|
flex: 1,
|
|
},
|
|
imageWithBorderRadius: {
|
|
borderRadius: 5,
|
|
},
|
|
imageSizeExample: {
|
|
width: 60,
|
|
height: 60,
|
|
backgroundColor: 'transparent',
|
|
marginRight: 10,
|
|
},
|
|
flexRow: {
|
|
flexDirection: 'row',
|
|
},
|
|
spaceBetweenView: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
},
|
|
customBorderColor: {
|
|
borderWidth: 5,
|
|
borderColor: '#f099f0',
|
|
},
|
|
borderTopLeftRadius: {
|
|
borderTopLeftRadius: 20,
|
|
},
|
|
opacity1: {
|
|
opacity: 1,
|
|
},
|
|
opacity2: {
|
|
opacity: 0.8,
|
|
},
|
|
opacity3: {
|
|
opacity: 0.6,
|
|
},
|
|
opacity4: {
|
|
opacity: 0.4,
|
|
},
|
|
opacity5: {
|
|
opacity: 0.2,
|
|
},
|
|
opacity6: {
|
|
opacity: 0,
|
|
},
|
|
transparentImageBackground: {
|
|
width: 60,
|
|
height: 60,
|
|
backgroundColor: 'transparent',
|
|
},
|
|
tintColor1: {
|
|
tintColor: '#ff2d55',
|
|
},
|
|
tintColor2: {
|
|
tintColor: '#5ac8fa',
|
|
},
|
|
tintColor3: {
|
|
tintColor: '#4cd964',
|
|
},
|
|
tintColor4: {
|
|
tintColor: '#8e8e93',
|
|
},
|
|
objectFitContain: {
|
|
objectFit: 'contain',
|
|
},
|
|
objectFitCover: {
|
|
objectFit: 'cover',
|
|
},
|
|
objectFitFill: {
|
|
objectFit: 'fill',
|
|
},
|
|
objectFitScaleDown: {
|
|
objectFit: 'scale-down',
|
|
},
|
|
objectFitNone: {
|
|
objectFit: 'none',
|
|
},
|
|
imageInBundle: {
|
|
borderColor: 'yellow',
|
|
borderWidth: 4,
|
|
},
|
|
imageInAssetCatalog: {
|
|
marginLeft: 10,
|
|
borderColor: 'blue',
|
|
borderWidth: 4,
|
|
},
|
|
backgroundColor1: {
|
|
backgroundColor: 'rgba(0, 0, 100, 0.25)',
|
|
},
|
|
backgroundColor2: {
|
|
backgroundColor: 'red',
|
|
},
|
|
backgroundColor3: {
|
|
backgroundColor: 'red',
|
|
borderColor: 'green',
|
|
borderWidth: 3,
|
|
borderRadius: 25,
|
|
},
|
|
borderRadius1: {
|
|
borderRadius: 19,
|
|
},
|
|
borderRadius2: {
|
|
borderWidth: 4,
|
|
borderTopLeftRadius: 10,
|
|
borderBottomRightRadius: 20,
|
|
borderColor: 'green',
|
|
},
|
|
borderRadius3: {
|
|
resizeMode: 'cover',
|
|
width: 90,
|
|
borderWidth: 4,
|
|
borderTopLeftRadius: 10,
|
|
borderTopRightRadius: 20,
|
|
borderBottomRightRadius: 30,
|
|
borderBottomLeftRadius: 40,
|
|
borderColor: 'red',
|
|
},
|
|
borderRadius4: {
|
|
resizeMode: 'stretch',
|
|
width: 90,
|
|
borderWidth: 4,
|
|
borderTopLeftRadius: 10,
|
|
borderTopRightRadius: 20,
|
|
borderBottomRightRadius: 30,
|
|
borderBottomLeftRadius: 40,
|
|
borderColor: 'red',
|
|
backgroundColor: 'yellow',
|
|
},
|
|
borderRadius5: {
|
|
resizeMode: 'contain',
|
|
width: 90,
|
|
borderWidth: 4,
|
|
borderTopLeftRadius: 10,
|
|
borderTopRightRadius: 20,
|
|
borderBottomRightRadius: 30,
|
|
borderBottomLeftRadius: 40,
|
|
borderColor: 'red',
|
|
backgroundColor: 'yellow',
|
|
},
|
|
boxShadow: {
|
|
margin: 10,
|
|
},
|
|
boxShadowWithBackground: {
|
|
backgroundColor: 'lightblue',
|
|
boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.5)',
|
|
},
|
|
boxShadowMultiOutsetInset: {
|
|
boxShadow:
|
|
'-5px -5px 10px 2px rgba(0, 128, 0, 0.5), 5px 5px 10px 2px rgba(128, 0, 0, 0.5), inset orange 0px 0px 20px 0px, black 0px 0px 5px 1px',
|
|
borderColor: 'blue',
|
|
borderWidth: 1,
|
|
borderRadius: 20,
|
|
},
|
|
boxShadowAsymetricallyRounded: {
|
|
borderTopLeftRadius: 0,
|
|
borderTopRightRadius: 5,
|
|
borderBottomRightRadius: 10,
|
|
borderBottomLeftRadius: 20,
|
|
marginRight: 80,
|
|
marginTop: 40,
|
|
boxShadow: '80px 0px 10px 0px hotpink',
|
|
transform: 'rotate(-15deg)',
|
|
},
|
|
vectorDrawable: {
|
|
height: 64,
|
|
width: 64,
|
|
},
|
|
resizedImage: {
|
|
height: 100,
|
|
width: '500%',
|
|
},
|
|
cachePolicyAndroidButtonContainer: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
marginTop: 10,
|
|
},
|
|
});
|
|
|
|
exports.displayName = (undefined: ?string);
|
|
exports.framework = 'React';
|
|
exports.title = 'Image';
|
|
exports.category = 'Basic';
|
|
exports.description =
|
|
'Base component for displaying different types of images.';
|
|
|
|
exports.examples = [
|
|
{
|
|
title: 'Plain Network Image with `source` prop.',
|
|
description: ('If the `source` prop `uri` property is prefixed with ' +
|
|
'"http", then it will be downloaded from the network.': string),
|
|
render: function (): React.Node {
|
|
return <Image source={fullImage} style={styles.base} />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Plain Network Image with `src` prop.',
|
|
description: ('If the `src` prop is defined with ' +
|
|
'"http", then it will be downloaded from the network.': string),
|
|
render: function (): React.Node {
|
|
return <Image src={fullImage.uri} style={styles.base} />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Multiple Image Source using the `srcSet` prop.',
|
|
description:
|
|
('A list of comma seperated uris along with scale are provided in `srcSet`.' +
|
|
'An appropriate value will be used based on the scale of the device.': string),
|
|
render: function (): React.Node {
|
|
return (
|
|
<Image
|
|
width={64}
|
|
height={64}
|
|
srcSet={`${IMAGE2} 4x, ${IMAGE1} 2x`}
|
|
style={styles.base}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Plain Blob Image',
|
|
description: ('If the `source` prop `uri` property is an object URL, ' +
|
|
'then it will be resolved using `BlobProvider` (Android) or `RCTBlobManager` (iOS).': string),
|
|
render: function (): React.Node {
|
|
return <BlobImageExample urls={[IMAGE1, IMAGE2]} />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Plain Static Image',
|
|
description:
|
|
('Static assets should be placed in the source code tree, and ' +
|
|
'required in the same way as JavaScript modules.': string),
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal}>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={styles.icon}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_selected.png')}
|
|
style={styles.icon}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_comment_normal.png')}
|
|
style={styles.icon}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_comment_highlighted.png')}
|
|
style={styles.icon}
|
|
/>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Image Loading Events',
|
|
render: function (): React.Node {
|
|
return (
|
|
<NetworkImageCallbackExample
|
|
source={{
|
|
uri: `${IMAGE1}?r=1&t=${Date.now()}`,
|
|
}}
|
|
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Error Handler',
|
|
render: function (): React.Node {
|
|
return (
|
|
<NetworkImageExample
|
|
source={{
|
|
uri: IMAGE1 + '_TYPO',
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Error Handler for Large Images',
|
|
render: function (): React.Node {
|
|
return (
|
|
<NetworkImageExample
|
|
resizeMethod="none"
|
|
// 6000x5340 ~ 128 MB
|
|
source={require('../../assets/very-large-image.png')}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Image Download Progress',
|
|
render: function (): React.Node {
|
|
return (
|
|
<NetworkImageExample
|
|
source={{
|
|
uri: `${IMAGE1}?r=1`,
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'defaultSource',
|
|
description: 'Show a placeholder image when a network image is loading',
|
|
render: function (): React.Node {
|
|
return (
|
|
<Image
|
|
defaultSource={require('../../assets/bunny.png')}
|
|
source={{
|
|
// Note: Use a large image and bust cache so we can in fact
|
|
// visualize the `defaultSource` image.
|
|
uri: fullImage.uri + '?cacheBust=notinCache' + Date.now(),
|
|
}}
|
|
style={styles.base}
|
|
/>
|
|
);
|
|
},
|
|
platform: 'ios',
|
|
},
|
|
{
|
|
title: 'Cache Policy',
|
|
description: `- First image will be loaded and cached.
|
|
- Second image is the same but will be reloaded if re-rendered as the cache policy is set to reload.
|
|
- Third image will try to load from the cache first and only use the network if the cached version is unavailable.
|
|
- Fourth image will never be loaded as the cache policy is set to only-if-cached and the image has not been loaded before.`,
|
|
render: function (): React.Node {
|
|
return <CacheControlExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Borders',
|
|
name: 'borders',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal} testID="borders-example">
|
|
<Image
|
|
source={smallImage}
|
|
style={[styles.base, styles.background, styles.customBorderColor]}
|
|
/>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Border Radius',
|
|
name: 'border-radius',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal} testID="border-radius-example">
|
|
<Image
|
|
style={[styles.base, styles.imageWithBorderRadius]}
|
|
source={fullImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.borderRadius1]}
|
|
source={fullImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.borderTopLeftRadius]}
|
|
source={fullImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.borderRadius2]}
|
|
source={fullImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.borderRadius3]}
|
|
source={fullImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.borderRadius4]}
|
|
source={fullImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.borderRadius5]}
|
|
source={fullImage}
|
|
/>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Background Color',
|
|
name: 'background-color',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal} testID="background-color-example">
|
|
<Image source={smallImage} style={styles.base} />
|
|
<Image
|
|
style={[styles.base, styles.backgroundColor1]}
|
|
source={smallImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.backgroundColor2]}
|
|
source={smallImage}
|
|
/>
|
|
<Image
|
|
style={[styles.base, styles.backgroundColor3]}
|
|
source={smallImage}
|
|
/>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Box Shadow',
|
|
name: 'box-shadow',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal} testID="box-shadow-example">
|
|
<Image
|
|
style={[
|
|
styles.base,
|
|
styles.boxShadow,
|
|
styles.boxShadowWithBackground,
|
|
]}
|
|
source={smallImage}
|
|
/>
|
|
<Image
|
|
style={[
|
|
styles.base,
|
|
styles.boxShadow,
|
|
styles.boxShadowMultiOutsetInset,
|
|
]}
|
|
source={smallImage}
|
|
/>
|
|
<Image
|
|
style={[
|
|
styles.base,
|
|
styles.boxShadow,
|
|
styles.boxShadowAsymetricallyRounded,
|
|
]}
|
|
source={fullImage}
|
|
/>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Opacity',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal}>
|
|
<Image style={[styles.base, styles.opacity1]} source={fullImage} />
|
|
<Image style={[styles.base, styles.opacity2]} source={fullImage} />
|
|
<Image style={[styles.base, styles.opacity3]} source={fullImage} />
|
|
<Image style={[styles.base, styles.opacity4]} source={fullImage} />
|
|
<Image style={[styles.base, styles.opacity5]} source={fullImage} />
|
|
<Image style={[styles.base, styles.opacity6]} source={fullImage} />
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Nesting content inside <Image> component',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.base}>
|
|
<Image
|
|
style={{...StyleSheet.absoluteFillObject}}
|
|
source={fullImage}
|
|
/>
|
|
<Text style={styles.nestedText}>React</Text>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Nesting content inside <ImageBackground> component',
|
|
render: function (): React.Node {
|
|
return (
|
|
<ImageBackground
|
|
style={styles.transparentImageBackground}
|
|
source={fullImage}>
|
|
<Text style={styles.nestedText}>React</Text>
|
|
</ImageBackground>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Tint Color',
|
|
description: ('The `tintColor` prop changes all the non-alpha ' +
|
|
'pixels to the tint color.': string),
|
|
render: function (): React.Node {
|
|
return (
|
|
<View>
|
|
<View style={styles.horizontal}>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor1,
|
|
]}
|
|
tintColor={'#5ac8fa'}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[styles.icon, styles.imageWithBorderRadius]}
|
|
tintColor={'#4cd964'}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[styles.icon, styles.imageWithBorderRadius]}
|
|
tintColor={'#ff2d55'}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[styles.icon, styles.imageWithBorderRadius]}
|
|
tintColor={'#8e8e93'}
|
|
/>
|
|
</View>
|
|
<RNTesterText style={styles.sectionText} variant="label">
|
|
It also works using the `tintColor` style prop
|
|
</RNTesterText>
|
|
<View style={styles.horizontal}>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor2,
|
|
]}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor3,
|
|
]}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor1,
|
|
]}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor4,
|
|
]}
|
|
/>
|
|
</View>
|
|
<RNTesterText style={styles.sectionText} variant="label">
|
|
The `tintColor` prop has precedence over the `tintColor` style prop
|
|
</RNTesterText>
|
|
<View style={styles.horizontal}>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor2,
|
|
]}
|
|
tintColor={'#5ac8fa'}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor3,
|
|
]}
|
|
tintColor={'#5ac8fa'}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor1,
|
|
]}
|
|
tintColor={'#5ac8fa'}
|
|
/>
|
|
<Image
|
|
source={require('../../assets/uie_thumb_normal.png')}
|
|
style={[
|
|
styles.icon,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor4,
|
|
]}
|
|
tintColor={'#5ac8fa'}
|
|
/>
|
|
</View>
|
|
<RNTesterText style={styles.sectionText} variant="label">
|
|
It also works with downloaded images:
|
|
</RNTesterText>
|
|
<View style={styles.horizontal}>
|
|
<Image
|
|
source={smallImage}
|
|
style={[
|
|
styles.base,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor2,
|
|
]}
|
|
/>
|
|
<Image
|
|
source={smallImage}
|
|
style={[
|
|
styles.base,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor3,
|
|
]}
|
|
/>
|
|
<Image
|
|
source={smallImage}
|
|
style={[
|
|
styles.base,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor1,
|
|
]}
|
|
/>
|
|
<Image
|
|
source={smallImage}
|
|
style={[
|
|
styles.base,
|
|
styles.imageWithBorderRadius,
|
|
styles.tintColor4,
|
|
]}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Object Fit',
|
|
description: ('The `objectFit` style prop controls how the image is ' +
|
|
'rendered within the frame.': string),
|
|
render: function (): React.Node {
|
|
return (
|
|
<View>
|
|
{[smallImage, fullImage].map((image, index) => {
|
|
return (
|
|
<View key={index}>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Contain
|
|
</RNTesterText>
|
|
<Image
|
|
style={[styles.resizeMode, styles.objectFitContain]}
|
|
source={image}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Cover
|
|
</RNTesterText>
|
|
<Image
|
|
style={[styles.resizeMode, styles.objectFitCover]}
|
|
source={image}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Fill
|
|
</RNTesterText>
|
|
<Image
|
|
style={[styles.resizeMode, styles.objectFitFill]}
|
|
source={image}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Scale Down
|
|
</RNTesterText>
|
|
<Image
|
|
style={[styles.resizeMode, styles.objectFitScaleDown]}
|
|
source={image}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
None
|
|
</RNTesterText>
|
|
<Image
|
|
style={[styles.resizeMode, styles.objectFitNone]}
|
|
source={image}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Resize Mode',
|
|
description: ('The `resizeMode` style prop controls how the image is ' +
|
|
'rendered within the frame.': string),
|
|
render: function (): React.Node {
|
|
return (
|
|
<View>
|
|
{[smallImage, fullImage].map((image, index) => {
|
|
return (
|
|
<View key={index}>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Contain
|
|
</RNTesterText>
|
|
<Image
|
|
style={styles.resizeMode}
|
|
resizeMode="contain"
|
|
source={image}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Cover
|
|
</RNTesterText>
|
|
<Image
|
|
style={styles.resizeMode}
|
|
resizeMode="cover"
|
|
source={image}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Stretch
|
|
</RNTesterText>
|
|
<Image
|
|
style={styles.resizeMode}
|
|
resizeMode="stretch"
|
|
source={image}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Repeat
|
|
</RNTesterText>
|
|
<Image
|
|
style={styles.resizeMode}
|
|
resizeMode="repeat"
|
|
source={image}
|
|
/>
|
|
</View>
|
|
<View style={styles.leftMargin}>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
Center
|
|
</RNTesterText>
|
|
<Image
|
|
style={styles.resizeMode}
|
|
resizeMode="center"
|
|
source={image}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.horizontal}>
|
|
<View>
|
|
<RNTesterText style={styles.resizeModeText}>
|
|
None
|
|
</RNTesterText>
|
|
<Image
|
|
style={styles.resizeMode}
|
|
resizeMode="none"
|
|
source={image}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Animated GIF',
|
|
render: function (): React.Node {
|
|
return (
|
|
<Image
|
|
style={styles.gif}
|
|
source={require('../../assets/tumblr_mfqekpMktw1rn90umo1_500.gif')}
|
|
/>
|
|
);
|
|
},
|
|
platform: 'ios',
|
|
},
|
|
{
|
|
title: 'Base64 image',
|
|
render: function (): React.Node {
|
|
return (
|
|
<Image style={styles.base64} source={{uri: base64Icon, scale: 3}} />
|
|
);
|
|
},
|
|
platform: 'ios',
|
|
},
|
|
{
|
|
title: 'Cap Insets',
|
|
description:
|
|
('When the image is resized, the corners of the size specified ' +
|
|
'by capInsets will stay a fixed size, but the center content and ' +
|
|
'borders of the image will be stretched. This is useful for creating ' +
|
|
'resizable rounded buttons, shadows, and other resizable assets.': string),
|
|
render: function (): React.Node {
|
|
return <ImageCapInsetsExample />;
|
|
},
|
|
platform: 'ios',
|
|
},
|
|
{
|
|
title: 'Image Size',
|
|
render: function (): React.Node {
|
|
return <ImageSizeExample source={fullImage} />;
|
|
},
|
|
},
|
|
{
|
|
title: 'MultipleSourcesExample',
|
|
description:
|
|
('The `source` prop allows passing in an array of uris, so that native to choose which image ' +
|
|
'to diplay based on the size of the of the target image': string),
|
|
render: function (): React.Node {
|
|
return <MultipleSourcesExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Legacy local image',
|
|
description: ('Images shipped with the native bundle, but not managed ' +
|
|
'by the JS packager': string),
|
|
render: function (): React.Node {
|
|
return <Image source={{uri: 'legacy_image', width: 120, height: 120}} />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Bundled images',
|
|
description: 'Images shipped in a separate native bundle',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.flexRow}>
|
|
<Image
|
|
source={{
|
|
uri: 'ImageInBundle',
|
|
bundle: 'RNTesterBundle',
|
|
width: 100,
|
|
height: 100,
|
|
}}
|
|
style={styles.imageInBundle}
|
|
/>
|
|
<Image
|
|
source={{
|
|
uri: 'ImageInAssetCatalog',
|
|
bundle: 'RNTesterBundle',
|
|
width: 100,
|
|
height: 100,
|
|
}}
|
|
style={styles.imageInAssetCatalog}
|
|
/>
|
|
</View>
|
|
);
|
|
},
|
|
platform: 'ios',
|
|
},
|
|
{
|
|
title: 'Blur Radius',
|
|
render: function (): React.Node {
|
|
return (
|
|
<View style={styles.horizontal}>
|
|
<Image style={styles.base} source={fullImage} blurRadius={0} />
|
|
<Image style={styles.base} source={fullImage} blurRadius={5} />
|
|
<Image style={styles.base} source={fullImage} blurRadius={10} />
|
|
<Image style={styles.base} source={fullImage} blurRadius={15} />
|
|
<Image style={styles.base} source={fullImage} blurRadius={20} />
|
|
<Image style={styles.base} source={fullImage} blurRadius={25} />
|
|
</View>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Accessibility',
|
|
description:
|
|
('If the `accessible` (boolean) prop is set to True, the image will be indicated as an accessbility element.': string),
|
|
render: function (): React.Node {
|
|
return <Image accessible source={fullImage} style={styles.base} />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Accessibility Label',
|
|
description:
|
|
('When an element is marked as accessibile (using the accessibility prop), it is good practice to set an accessibilityLabel on the image to provide a description of the element to people who use VoiceOver. VoiceOver will read this string when people select this element.': string),
|
|
render: function (): React.Node {
|
|
return (
|
|
<Image
|
|
accessible
|
|
accessibilityLabel="Picture of people standing around a table"
|
|
source={fullImage}
|
|
style={styles.base}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Accessibility Label via alt prop',
|
|
description:
|
|
'Using the alt prop markes an element as being accessibile, and passes the alt text to accessibilityLabel',
|
|
render: function (): React.Node {
|
|
return (
|
|
<Image
|
|
alt="Picture of people standing around a table"
|
|
source={fullImage}
|
|
style={styles.base}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Fade Duration',
|
|
description:
|
|
('The time (in miliseconds) that an image will fade in for when it appears (default = 300).': string),
|
|
render: function (): React.Node {
|
|
return <FadeDurationExample />;
|
|
},
|
|
platform: 'android',
|
|
},
|
|
{
|
|
title: 'Loading Indicator Source',
|
|
description:
|
|
('This prop is used to set the resource that will be used as the loading indicator for the image (displayed until the image is ready to be displayed).': string),
|
|
render: function (): React.Node {
|
|
return <LoadingIndicatorSourceExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'On Layout',
|
|
description:
|
|
('This prop is used to set the handler function to be called when the image is mounted or its layout changes. The function receives an event with `{nativeEvent: {layout: {x, y, width, height}}}`': string),
|
|
render: function (): React.Node {
|
|
return <OnLayoutExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'On Partial Load',
|
|
description:
|
|
('This prop is used to set the handler function to be called when the partial load of the image is complete. This is meant for progressive JPEG loads.': string),
|
|
render: function (): React.Node {
|
|
return <OnPartialLoadExample />;
|
|
},
|
|
platform: 'ios',
|
|
},
|
|
{
|
|
title: 'Vector Drawable',
|
|
name: 'vector-drawable',
|
|
description:
|
|
'Demonstrating an example of loading a vector drawable asset by name',
|
|
render: function (): React.Node {
|
|
return <VectorDrawableExample />;
|
|
},
|
|
platform: 'android',
|
|
},
|
|
{
|
|
title: 'Large image with different resize methods',
|
|
name: 'resize-method',
|
|
description:
|
|
'Demonstrating the effects of loading a large image with different resize methods',
|
|
scrollable: true,
|
|
render: function (): React.Node {
|
|
const methods: Array<ImageProps['resizeMethod']> = [
|
|
'auto',
|
|
'resize',
|
|
'scale',
|
|
'none',
|
|
];
|
|
// Four copies of the same image so we don't serve cached copies of the same image
|
|
const images = [
|
|
require('../../assets/large-image-1.png'),
|
|
require('../../assets/large-image-2.png'),
|
|
require('../../assets/large-image-3.png'),
|
|
require('../../assets/large-image-4.png'),
|
|
];
|
|
return (
|
|
<View testID="resize-method-example">
|
|
{methods.map((method, index) => (
|
|
<View
|
|
key={method}
|
|
style={{display: 'flex', overflow: 'hidden'}}
|
|
testID={`resize-method-example-${method ?? ''}`}>
|
|
<RNTesterText>{method}</RNTesterText>
|
|
<Image
|
|
resizeMethod={method}
|
|
source={images[index]}
|
|
style={styles.resizedImage}
|
|
/>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
},
|
|
platform: 'android',
|
|
},
|
|
] as Array<RNTesterModuleExample>;
|