mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
add onLoad prop to Image component (#2293)
# Summary Closes #1442 We want to add new props to the Image Component. ## Test Plan Added the Test component. Manually test that in Android and IOS platforms on new and old Architectures. ### What are the steps to reproduce (after prerequisites)? ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | Android | ✅ |
This commit is contained in:
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import ColorTest from './src/ColorTest';
|
||||
import PointerEventsBoxNone from './src/PointerEventsBoxNone';
|
||||
import Test1374 from './src/Test1374';
|
||||
import Test1442 from './src/Test1442';
|
||||
import Test1451 from './src/Test1451';
|
||||
import Test1718 from './src/Test1718';
|
||||
import Test1813 from './src/Test1813';
|
||||
|
||||
BIN
TestsExample/images/arrow.png
Normal file
BIN
TestsExample/images/arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
TestsExample/images/earth.jpg
Normal file
BIN
TestsExample/images/earth.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
116
TestsExample/src/Test1442.tsx
Normal file
116
TestsExample/src/Test1442.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, {useState} from 'react';
|
||||
import {ImageLoadEventData, Platform, Image as RNImage} from 'react-native';
|
||||
import {Svg, Image} from 'react-native-svg';
|
||||
|
||||
export default function Test1442() {
|
||||
return <TestWithStrictSize />;
|
||||
}
|
||||
|
||||
function TestRNImage() {
|
||||
const [state, setState] = useState<ImageLoadEventData['source']>();
|
||||
console.log(`${Platform.OS} state:`, state);
|
||||
|
||||
return (
|
||||
<RNImage
|
||||
style={{width: state?.width || '100%', height: state?.height || '100%'}}
|
||||
source={{
|
||||
uri: 'https://image-placeholder.com/images/actual-size/75x75.png',
|
||||
}}
|
||||
onLoad={e => {
|
||||
setState(e.nativeEvent.source as any);
|
||||
console.log(
|
||||
`RNImage:${Platform.OS} load PNG image from url with strict size`,
|
||||
e.nativeEvent,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
function TestWithStrictSize(): React.JSX.Element {
|
||||
const [state, setState] = useState<
|
||||
ImageLoadEventData['source'] | undefined
|
||||
>();
|
||||
console.log(`${Platform.OS} state:`, state);
|
||||
return (
|
||||
<Svg>
|
||||
<Image
|
||||
width={state?.width || '100%'}
|
||||
height={state?.height || '100%'}
|
||||
href={'https://image-placeholder.com/images/actual-size/75x75.png'}
|
||||
onLoad={e => {
|
||||
setState(e.nativeEvent);
|
||||
console.log(
|
||||
`Image:${Platform.OS} load PNG image from url with strict size`,
|
||||
e.nativeEvent,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
||||
const PNGImageFromUrl = () => {
|
||||
return (
|
||||
<Svg>
|
||||
<Image
|
||||
opacity="1"
|
||||
width={100}
|
||||
height={100}
|
||||
href={'https://static.thenounproject.com/png/1563361-200.png'}
|
||||
onLoad={e =>
|
||||
console.log(`${Platform.OS} load png image from url`, e.nativeEvent)
|
||||
}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
const PNGImageFromFile = () => {
|
||||
return (
|
||||
<Svg>
|
||||
<Image
|
||||
opacity="1"
|
||||
width={100}
|
||||
height={100}
|
||||
href={require('../images/arrow.png')}
|
||||
onLoad={e =>
|
||||
console.log(`${Platform.OS} load png image from file`, e.nativeEvent)
|
||||
}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
const JPEGImageFromUrl = () => {
|
||||
return (
|
||||
<Svg>
|
||||
<Image
|
||||
opacity="1"
|
||||
width={'100%'}
|
||||
height={'100%'}
|
||||
href={
|
||||
'https://images.unsplash.com/photo-1614730321146-b6fa6a46bcb4?q=80&w=6561&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'
|
||||
}
|
||||
onLoad={e =>
|
||||
console.log(`${Platform.OS} load JPEG image from url`, e.nativeEvent)
|
||||
}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
const JPEGImageFromFile = () => {
|
||||
return (
|
||||
<Svg>
|
||||
<Image
|
||||
opacity="1"
|
||||
width={'100%'}
|
||||
height={'100%'}
|
||||
href={require('../images/earth.jpg')}
|
||||
onLoad={e =>
|
||||
console.log(`${Platform.OS} load JPEG image from file`, e.nativeEvent)
|
||||
}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
@@ -30,8 +30,12 @@ import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.views.imagehelper.ImageSource;
|
||||
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
|
||||
import com.horcrux.svg.events.SvgLoadEvent;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -137,6 +141,15 @@ class ImageView extends RenderableView {
|
||||
new BaseBitmapDataSubscriber() {
|
||||
@Override
|
||||
public void onNewResultImpl(Bitmap bitmap) {
|
||||
final EventDispatcher mEventDispatcher =
|
||||
UIManagerHelper.getEventDispatcherForReactTag(mContext, getId());
|
||||
mEventDispatcher.dispatchEvent(new SvgLoadEvent(
|
||||
UIManagerHelper.getSurfaceId(ImageView.this),
|
||||
getId(),
|
||||
mContext,
|
||||
uriString,
|
||||
bitmap.getWidth(),
|
||||
bitmap.getHeight()));
|
||||
mLoading.set(false);
|
||||
SvgView view = getSvgView();
|
||||
if (view != null) {
|
||||
|
||||
@@ -82,6 +82,7 @@ import com.facebook.react.bridge.JavaOnlyMap;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.MatrixMathHelper;
|
||||
@@ -134,7 +135,10 @@ import com.facebook.react.viewmanagers.RNSVGTextPathManagerDelegate;
|
||||
import com.facebook.react.viewmanagers.RNSVGTextPathManagerInterface;
|
||||
import com.facebook.react.viewmanagers.RNSVGUseManagerDelegate;
|
||||
import com.facebook.react.viewmanagers.RNSVGUseManagerInterface;
|
||||
import com.horcrux.svg.events.SvgLoadEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -926,6 +930,12 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
|
||||
public void setMeetOrSlice(ImageView node, int meetOrSlice) {
|
||||
node.setMeetOrSlice(meetOrSlice);
|
||||
}
|
||||
|
||||
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||
Map<String, Object> eventTypes = new HashMap<>();
|
||||
eventTypes.put(SvgLoadEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoad"));
|
||||
return eventTypes;
|
||||
}
|
||||
}
|
||||
|
||||
static class CircleViewManager extends RenderableViewManager<CircleView>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.horcrux.svg.events;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.imagehelper.ImageSource;
|
||||
|
||||
public class SvgLoadEvent extends Event<SvgLoadEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topLoad";
|
||||
private final float width;
|
||||
private final float height;
|
||||
private final String uri;
|
||||
|
||||
public SvgLoadEvent(int surfaceId, int viewId, ReactContext mContext, String uriString, float width, float height) {
|
||||
super(surfaceId, viewId);
|
||||
ImageSource imageSource = new ImageSource(mContext, uriString);
|
||||
this.uri = imageSource.getSource();;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), getEventData());
|
||||
}
|
||||
|
||||
protected WritableMap getEventData() {
|
||||
WritableMap eventData = Arguments.createMap();
|
||||
eventData.putDouble("width", width);
|
||||
eventData.putDouble("height", height);
|
||||
eventData.putString("uri", uri);
|
||||
return eventData;
|
||||
}
|
||||
}
|
||||
@@ -32,5 +32,6 @@
|
||||
@property (nonatomic, strong) RNSVGLength *imageheight;
|
||||
@property (nonatomic, strong) NSString *align;
|
||||
@property (nonatomic, assign) RNSVGVBMOS meetOrSlice;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoad;
|
||||
|
||||
@end
|
||||
|
||||
@@ -133,6 +133,10 @@ using namespace facebook::react;
|
||||
// See for more info: T46311063.
|
||||
return;
|
||||
}
|
||||
auto imageSource = _state->getData().getImageSource();
|
||||
imageSource.size = {image.size.width, image.size.height};
|
||||
static_cast<const RNSVGImageEventEmitter &>(*_eventEmitter).onLoad({imageSource.size.width, imageSource.size.height, imageSource.uri});
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_image = CGImageRetain(image.CGImage);
|
||||
self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image));
|
||||
@@ -202,6 +206,12 @@ using namespace facebook::react;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_image = CGImageRetain(image.CGImage);
|
||||
self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image));
|
||||
RCTImageSource *sourceLoaded = [src imageSourceWithSize:image.size scale:image.scale];
|
||||
self->_onLoad(@{
|
||||
@"width" : @(sourceLoaded.size.width),
|
||||
@"height" : @(sourceLoaded.size.height),
|
||||
@"uri" : sourceLoaded.request.URL.absoluteString
|
||||
});
|
||||
[self invalidate];
|
||||
});
|
||||
}];
|
||||
|
||||
@@ -36,5 +36,6 @@ RCT_CUSTOM_VIEW_PROPERTY(height, id, RNSVGImage)
|
||||
RCT_EXPORT_VIEW_PROPERTY(src, RCTImageSource)
|
||||
RCT_EXPORT_VIEW_PROPERTY(align, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(meetOrSlice, RNSVGVBMOS)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock);
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,7 +27,7 @@ JSI_EXPORT extern const char RNSVGImageComponentName[];
|
||||
class JSI_EXPORT RNSVGImageShadowNode final : public ConcreteViewShadowNode<
|
||||
RNSVGImageComponentName,
|
||||
RNSVGImageProps,
|
||||
ViewEventEmitter,
|
||||
RNSVGImageEventEmitter,
|
||||
RNSVGImageState> {
|
||||
public:
|
||||
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import type { ImageProps as RNImageProps, NativeMethods } from 'react-native';
|
||||
import type {
|
||||
ImageProps as RNImageProps,
|
||||
NativeMethods,
|
||||
NativeSyntheticEvent,
|
||||
} from 'react-native';
|
||||
import { Image } from 'react-native';
|
||||
import { alignEnum, meetOrSliceTypes } from '../lib/extract/extractViewBox';
|
||||
import { withoutXY } from '../lib/extract/extractProps';
|
||||
import type { CommonPathProps, NumberProp } from '../lib/extract/types';
|
||||
import Shape from './Shape';
|
||||
import RNSVGImage from '../fabric/ImageNativeComponent';
|
||||
import RNSVGImage, {
|
||||
type ImageLoadEventData,
|
||||
} from '../fabric/ImageNativeComponent';
|
||||
|
||||
const spacesRegExp = /\s+/;
|
||||
|
||||
@@ -18,6 +24,7 @@ export interface ImageProps extends CommonPathProps {
|
||||
href?: RNImageProps['source'] | string;
|
||||
preserveAspectRatio?: string;
|
||||
opacity?: NumberProp;
|
||||
onLoad?: (e: NativeSyntheticEvent<ImageLoadEventData>) => void;
|
||||
}
|
||||
|
||||
export default class SvgImage extends Shape<ImageProps> {
|
||||
@@ -41,6 +48,7 @@ export default class SvgImage extends Shape<ImageProps> {
|
||||
height,
|
||||
xlinkHref,
|
||||
href = xlinkHref,
|
||||
onLoad,
|
||||
} = props;
|
||||
const modes = preserveAspectRatio
|
||||
? preserveAspectRatio.trim().split(spacesRegExp)
|
||||
@@ -53,6 +61,7 @@ export default class SvgImage extends Shape<ImageProps> {
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
onLoad,
|
||||
meetOrSlice: meetOrSliceTypes[meetOrSlice] || 0,
|
||||
align: alignEnum[align] || 'xMidYMid',
|
||||
src: !href
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ImageSourcePropType as ImageSource,
|
||||
} from 'react-native';
|
||||
import type {
|
||||
DirectEventHandler,
|
||||
Float,
|
||||
Int32,
|
||||
WithDefault,
|
||||
@@ -14,6 +15,12 @@ import type { ViewProps } from './utils';
|
||||
import type { UnsafeMixed } from './codegenUtils';
|
||||
import { NumberProp } from '../lib/extract/types';
|
||||
|
||||
export type ImageLoadEventData = {
|
||||
width: Float;
|
||||
height: Float;
|
||||
uri: string;
|
||||
};
|
||||
|
||||
interface SvgNodeCommonProps {
|
||||
name?: string;
|
||||
opacity?: WithDefault<Float, 1.0>;
|
||||
@@ -62,6 +69,7 @@ interface NativeProps
|
||||
src?: ImageSource | null;
|
||||
align?: string;
|
||||
meetOrSlice?: Int32;
|
||||
onLoad?: DirectEventHandler<ImageLoadEventData>;
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<NativeProps>('RNSVGImage', {
|
||||
|
||||
Reference in New Issue
Block a user