Add support for ImageLoadEvents to RCTImageView
Summary: This diff implements ImageLoadEvents (ON_LOAD, ON_LOAD_END and ON_LOAD_START) in RCTImageView. ON_ERROR and ON_PROGRESS are easy to implement, too, but these 2 are supposed to carry extra information (error message and progress) but ImageLoadEvent doesn't support a payload yet (I'll add them in a separate patch). Reviewed By: ahmedre Differential Revision: D2824772
This commit is contained in:
parent
76abec8894
commit
59fe71caff
|
@ -14,4 +14,5 @@ import android.graphics.Bitmap;
|
||||||
/* package */ interface BitmapUpdateListener {
|
/* package */ interface BitmapUpdateListener {
|
||||||
public void onSecondaryAttach(Bitmap bitmap);
|
public void onSecondaryAttach(Bitmap bitmap);
|
||||||
public void onBitmapReady(Bitmap bitmap);
|
public void onBitmapReady(Bitmap bitmap);
|
||||||
|
public void onImageLoadEvent(int imageLoadEvent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,11 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
*/
|
*/
|
||||||
public ScaleType getScaleType();
|
public ScaleType getScaleType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React tag used for dispatching ImageLoadEvents, or 0 to ignore events.
|
||||||
|
*/
|
||||||
|
public void setReactTag(int reactTag);
|
||||||
|
|
||||||
public void setBorderWidth(float borderWidth);
|
public void setBorderWidth(float borderWidth);
|
||||||
|
|
||||||
public float getBorderWidth();
|
public float getBorderWidth();
|
||||||
|
|
|
@ -14,19 +14,23 @@ import javax.annotation.Nullable;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
|
||||||
|
import com.facebook.drawee.controller.ControllerListener;
|
||||||
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||||
import com.facebook.drawee.generic.RoundingParams;
|
import com.facebook.drawee.generic.RoundingParams;
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
import com.facebook.infer.annotation.Assertions;
|
import com.facebook.infer.annotation.Assertions;
|
||||||
|
import com.facebook.react.views.image.ImageLoadEvent;
|
||||||
import com.facebook.react.views.image.ImageResizeMode;
|
import com.facebook.react.views.image.ImageResizeMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DrawImageWithDrawee is DrawCommand that can draw a local or remote image.
|
* DrawImageWithDrawee is DrawCommand that can draw a local or remote image.
|
||||||
* It uses DraweeRequestHelper internally to fetch and cache the images.
|
* It uses DraweeRequestHelper internally to fetch and cache the images.
|
||||||
*/
|
*/
|
||||||
/* package */ final class DrawImageWithDrawee extends AbstractDrawCommand implements DrawImage {
|
/* package */ final class DrawImageWithDrawee extends AbstractDrawCommand
|
||||||
|
implements DrawImage, ControllerListener {
|
||||||
|
|
||||||
private @Nullable DraweeRequestHelper mRequestHelper;
|
private @Nullable DraweeRequestHelper mRequestHelper;
|
||||||
private @Nullable PorterDuffColorFilter mColorFilter;
|
private @Nullable PorterDuffColorFilter mColorFilter;
|
||||||
|
@ -34,6 +38,8 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
private float mBorderWidth;
|
private float mBorderWidth;
|
||||||
private float mBorderRadius;
|
private float mBorderRadius;
|
||||||
private int mBorderColor;
|
private int mBorderColor;
|
||||||
|
private int mReactTag;
|
||||||
|
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasImageRequest() {
|
public boolean hasImageRequest() {
|
||||||
|
@ -45,7 +51,7 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
if (imageRequest == null) {
|
if (imageRequest == null) {
|
||||||
mRequestHelper = null;
|
mRequestHelper = null;
|
||||||
} else {
|
} else {
|
||||||
mRequestHelper = new DraweeRequestHelper(imageRequest);
|
mRequestHelper = new DraweeRequestHelper(imageRequest, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +104,11 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
return mBorderColor;
|
return mBorderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReactTag(int reactTag) {
|
||||||
|
mReactTag = reactTag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDraw(Canvas canvas) {
|
public void onDraw(Canvas canvas) {
|
||||||
Assertions.assumeNotNull(mRequestHelper).getDrawable().draw(canvas);
|
Assertions.assumeNotNull(mRequestHelper).getDrawable().draw(canvas);
|
||||||
|
@ -105,6 +116,8 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
||||||
|
mCallback = callback;
|
||||||
|
|
||||||
GenericDraweeHierarchy hierarchy = Assertions.assumeNotNull(mRequestHelper).getHierarchy();
|
GenericDraweeHierarchy hierarchy = Assertions.assumeNotNull(mRequestHelper).getHierarchy();
|
||||||
|
|
||||||
RoundingParams roundingParams = hierarchy.getRoundingParams();
|
RoundingParams roundingParams = hierarchy.getRoundingParams();
|
||||||
|
@ -140,6 +153,43 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
Assertions.assumeNotNull(mRequestHelper).detach();
|
Assertions.assumeNotNull(mRequestHelper).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubmit(String id, Object callerContext) {
|
||||||
|
if (mCallback != null && mReactTag != 0) {
|
||||||
|
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinalImageSet(
|
||||||
|
String id,
|
||||||
|
@Nullable Object imageInfo,
|
||||||
|
@Nullable Animatable animatable) {
|
||||||
|
if (mCallback != null && mReactTag != 0) {
|
||||||
|
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
|
||||||
|
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIntermediateImageSet(String id, @Nullable Object imageInfo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIntermediateImageFailed(String id, Throwable throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(String id, Throwable throwable) {
|
||||||
|
if (mCallback != null && mReactTag != 0) {
|
||||||
|
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRelease(String id) {
|
||||||
|
}
|
||||||
|
|
||||||
private boolean shouldDisplayBorder() {
|
private boolean shouldDisplayBorder() {
|
||||||
return mBorderColor != 0 || mBorderRadius >= 0.5f;
|
return mBorderColor != 0 || mBorderRadius >= 0.5f;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
private @Nullable Path mPathForRoundedBitmap;
|
private @Nullable Path mPathForRoundedBitmap;
|
||||||
private @Nullable BitmapShader mBitmapShader;
|
private @Nullable BitmapShader mBitmapShader;
|
||||||
private boolean mForceClip;
|
private boolean mForceClip;
|
||||||
|
private int mReactTag;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasImageRequest() {
|
public boolean hasImageRequest() {
|
||||||
|
@ -80,6 +81,11 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
return mScaleType;
|
return mScaleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReactTag(int reactTag) {
|
||||||
|
mReactTag = reactTag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
Bitmap bitmap = Assertions.assumeNotNull(mRequestHelper).getBitmap();
|
Bitmap bitmap = Assertions.assumeNotNull(mRequestHelper).getBitmap();
|
||||||
|
@ -148,6 +154,13 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
updateBounds(bitmap);
|
updateBounds(bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageLoadEvent(int imageLoadEvent) {
|
||||||
|
if (mReactTag != 0 && mCallback != null) {
|
||||||
|
mCallback.dispatchImageLoadEvent(mReactTag, imageLoadEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* package */ void updateBounds(Bitmap bitmap) {
|
/* package */ void updateBounds(Bitmap bitmap) {
|
||||||
Assertions.assumeNotNull(mCallback).invalidate();
|
Assertions.assumeNotNull(mCallback).invalidate();
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.content.res.Resources;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
||||||
|
import com.facebook.drawee.controller.ControllerListener;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
import com.facebook.drawee.interfaces.DraweeController;
|
import com.facebook.drawee.interfaces.DraweeController;
|
||||||
|
@ -37,10 +38,11 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
private final DraweeController mDraweeController;
|
private final DraweeController mDraweeController;
|
||||||
private int mAttachCounter;
|
private int mAttachCounter;
|
||||||
|
|
||||||
/* package */ DraweeRequestHelper(ImageRequest imageRequest) {
|
/* package */ DraweeRequestHelper(ImageRequest imageRequest, ControllerListener listener) {
|
||||||
DraweeController controller = sControllerBuilder
|
DraweeController controller = sControllerBuilder
|
||||||
.setImageRequest(imageRequest)
|
.setImageRequest(imageRequest)
|
||||||
.setCallerContext(RCTImageView.getCallerContext())
|
.setCallerContext(RCTImageView.getCallerContext())
|
||||||
|
.setControllerListener(listener)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
controller.setHierarchy(sHierarchyBuilder.build());
|
controller.setHierarchy(sHierarchyBuilder.build());
|
||||||
|
|
|
@ -18,17 +18,21 @@ import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
import com.facebook.react.bridge.SoftAssertions;
|
import com.facebook.react.bridge.SoftAssertions;
|
||||||
import com.facebook.react.touch.CatalystInterceptingViewGroup;
|
import com.facebook.react.touch.CatalystInterceptingViewGroup;
|
||||||
import com.facebook.react.touch.OnInterceptTouchEventListener;
|
import com.facebook.react.touch.OnInterceptTouchEventListener;
|
||||||
import com.facebook.react.uimanager.PointerEvents;
|
import com.facebook.react.uimanager.PointerEvents;
|
||||||
import com.facebook.react.uimanager.ReactCompoundView;
|
import com.facebook.react.uimanager.ReactCompoundView;
|
||||||
import com.facebook.react.uimanager.ReactPointerEventsView;
|
import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||||
|
import com.facebook.react.uimanager.UIManagerModule;
|
||||||
|
import com.facebook.react.views.image.ImageLoadEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over
|
* A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over
|
||||||
|
@ -54,6 +58,18 @@ import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||||
view.invalidate();
|
view.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void dispatchImageLoadEvent(int reactTag, int imageLoadEvent) {
|
||||||
|
FlatViewGroup view = get();
|
||||||
|
if (view == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactContext reactContext = ((ReactContext) view.getContext());
|
||||||
|
UIManagerModule uiManagerModule = reactContext.getNativeModule(UIManagerModule.class);
|
||||||
|
uiManagerModule.getEventDispatcher().dispatchEvent(
|
||||||
|
new ImageLoadEvent(reactTag, SystemClock.uptimeMillis(), imageLoadEvent));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ArrayList<FlatViewGroup> LAYOUT_REQUESTS = new ArrayList<>();
|
private static final ArrayList<FlatViewGroup> LAYOUT_REQUESTS = new ArrayList<>();
|
||||||
|
|
|
@ -99,6 +99,11 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
Assertions.assumeNotNull(mCallback).invalidate();
|
Assertions.assumeNotNull(mCallback).invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageLoadEvent(int imageLoadEvent) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import com.facebook.imagepipeline.image.CloseableBitmap;
|
||||||
import com.facebook.imagepipeline.image.CloseableImage;
|
import com.facebook.imagepipeline.image.CloseableImage;
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
import com.facebook.infer.annotation.Assertions;
|
import com.facebook.infer.annotation.Assertions;
|
||||||
|
import com.facebook.react.views.image.ImageLoadEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for DrawImage that helps manage fetch requests through ImagePipeline.
|
* Helper class for DrawImage that helps manage fetch requests through ImagePipeline.
|
||||||
|
@ -54,11 +55,13 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
// this is a secondary attach, ignore it, only updating Bitmap boundaries if needed.
|
// this is a secondary attach, ignore it, only updating Bitmap boundaries if needed.
|
||||||
Bitmap bitmap = getBitmap();
|
Bitmap bitmap = getBitmap();
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
mBitmapUpdateListener.onSecondaryAttach(bitmap);
|
listener.onSecondaryAttach(bitmap);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_START);
|
||||||
|
|
||||||
Assertions.assertCondition(mDataSource == null);
|
Assertions.assertCondition(mDataSource == null);
|
||||||
Assertions.assertCondition(mImageRef == null);
|
Assertions.assertCondition(mImageRef == null);
|
||||||
|
|
||||||
|
@ -150,7 +153,10 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assertions.assumeNotNull(mBitmapUpdateListener).onBitmapReady(bitmap);
|
BitmapUpdateListener listener = Assertions.assumeNotNull(mBitmapUpdateListener);
|
||||||
|
listener.onBitmapReady(bitmap);
|
||||||
|
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
|
||||||
|
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD);
|
||||||
} finally {
|
} finally {
|
||||||
dataSource.close();
|
dataSource.close();
|
||||||
}
|
}
|
||||||
|
@ -158,8 +164,8 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||||
if (mDataSource != dataSource) {
|
if (mDataSource == dataSource) {
|
||||||
// Should always be the case, but let's be safe.
|
Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
|
||||||
mDataSource = null;
|
mDataSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +174,11 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||||
|
if (mDataSource == dataSource) {
|
||||||
|
mDataSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -80,6 +80,11 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "shouldNotifyLoadEvents")
|
||||||
|
public void setShouldNotifyLoadEvents(boolean shouldNotifyLoadEvents) {
|
||||||
|
getMutableDrawImage().setReactTag(shouldNotifyLoadEvents ? getReactTag() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = "src")
|
@ReactProp(name = "src")
|
||||||
public void setSource(@Nullable String source) {
|
public void setSource(@Nullable String source) {
|
||||||
getMutableDrawImage().setImageRequest(
|
getMutableDrawImage().setImageRequest(
|
||||||
|
|
Loading…
Reference in New Issue