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:
Denis Koroskin 2016-01-12 18:51:12 -08:00 committed by Ahmed El-Helw
parent 76abec8894
commit 59fe71caff
9 changed files with 115 additions and 7 deletions

View File

@ -14,4 +14,5 @@ import android.graphics.Bitmap;
/* package */ interface BitmapUpdateListener {
public void onSecondaryAttach(Bitmap bitmap);
public void onBitmapReady(Bitmap bitmap);
public void onImageLoadEvent(int imageLoadEvent);
}

View File

@ -44,6 +44,11 @@ import com.facebook.imagepipeline.request.ImageRequest;
*/
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 float getBorderWidth();

View File

@ -14,19 +14,23 @@ import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
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.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.views.image.ImageLoadEvent;
import com.facebook.react.views.image.ImageResizeMode;
/**
* DrawImageWithDrawee is DrawCommand that can draw a local or remote image.
* 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 PorterDuffColorFilter mColorFilter;
@ -34,6 +38,8 @@ import com.facebook.react.views.image.ImageResizeMode;
private float mBorderWidth;
private float mBorderRadius;
private int mBorderColor;
private int mReactTag;
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
@Override
public boolean hasImageRequest() {
@ -45,7 +51,7 @@ import com.facebook.react.views.image.ImageResizeMode;
if (imageRequest == null) {
mRequestHelper = null;
} else {
mRequestHelper = new DraweeRequestHelper(imageRequest);
mRequestHelper = new DraweeRequestHelper(imageRequest, this);
}
}
@ -98,6 +104,11 @@ import com.facebook.react.views.image.ImageResizeMode;
return mBorderColor;
}
@Override
public void setReactTag(int reactTag) {
mReactTag = reactTag;
}
@Override
public void onDraw(Canvas canvas) {
Assertions.assumeNotNull(mRequestHelper).getDrawable().draw(canvas);
@ -105,6 +116,8 @@ import com.facebook.react.views.image.ImageResizeMode;
@Override
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
mCallback = callback;
GenericDraweeHierarchy hierarchy = Assertions.assumeNotNull(mRequestHelper).getHierarchy();
RoundingParams roundingParams = hierarchy.getRoundingParams();
@ -140,6 +153,43 @@ import com.facebook.react.views.image.ImageResizeMode;
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() {
return mBorderColor != 0 || mBorderRadius >= 0.5f;
}

View File

@ -44,6 +44,7 @@ import com.facebook.react.views.image.ImageResizeMode;
private @Nullable Path mPathForRoundedBitmap;
private @Nullable BitmapShader mBitmapShader;
private boolean mForceClip;
private int mReactTag;
@Override
public boolean hasImageRequest() {
@ -80,6 +81,11 @@ import com.facebook.react.views.image.ImageResizeMode;
return mScaleType;
}
@Override
public void setReactTag(int reactTag) {
mReactTag = reactTag;
}
@Override
protected void onDraw(Canvas canvas) {
Bitmap bitmap = Assertions.assumeNotNull(mRequestHelper).getBitmap();
@ -148,6 +154,13 @@ import com.facebook.react.views.image.ImageResizeMode;
updateBounds(bitmap);
}
@Override
public void onImageLoadEvent(int imageLoadEvent) {
if (mReactTag != 0 && mCallback != null) {
mCallback.dispatchImageLoadEvent(mReactTag, imageLoadEvent);
}
}
/* package */ void updateBounds(Bitmap bitmap) {
Assertions.assumeNotNull(mCallback).invalidate();

View File

@ -14,6 +14,7 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
@ -37,10 +38,11 @@ import com.facebook.infer.annotation.Assertions;
private final DraweeController mDraweeController;
private int mAttachCounter;
/* package */ DraweeRequestHelper(ImageRequest imageRequest) {
/* package */ DraweeRequestHelper(ImageRequest imageRequest, ControllerListener listener) {
DraweeController controller = sControllerBuilder
.setImageRequest(imageRequest)
.setCallerContext(RCTImageView.getCallerContext())
.setControllerListener(listener)
.build();
controller.setHierarchy(sHierarchyBuilder.build());

View File

@ -18,17 +18,21 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.touch.CatalystInterceptingViewGroup;
import com.facebook.react.touch.OnInterceptTouchEventListener;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactCompoundView;
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
@ -54,6 +58,18 @@ import com.facebook.react.uimanager.ReactPointerEventsView;
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<>();

View File

@ -99,6 +99,11 @@ import com.facebook.infer.annotation.Assertions;
Assertions.assumeNotNull(mCallback).invalidate();
}
@Override
public void onImageLoadEvent(int imageLoadEvent) {
// ignore
}
@Override
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
mCallback = callback;

View File

@ -23,6 +23,7 @@ import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.views.image.ImageLoadEvent;
/**
* 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.
Bitmap bitmap = getBitmap();
if (bitmap != null) {
mBitmapUpdateListener.onSecondaryAttach(bitmap);
listener.onSecondaryAttach(bitmap);
}
return;
}
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_START);
Assertions.assertCondition(mDataSource == null);
Assertions.assertCondition(mImageRef == null);
@ -150,7 +153,10 @@ import com.facebook.infer.annotation.Assertions;
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 {
dataSource.close();
}
@ -158,8 +164,8 @@ import com.facebook.infer.annotation.Assertions;
@Override
public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (mDataSource != dataSource) {
// Should always be the case, but let's be safe.
if (mDataSource == dataSource) {
Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
mDataSource = null;
}
@ -168,6 +174,11 @@ import com.facebook.infer.annotation.Assertions;
@Override
public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (mDataSource == dataSource) {
mDataSource = null;
}
dataSource.close();
}
@Override

View File

@ -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")
public void setSource(@Nullable String source) {
getMutableDrawImage().setImageRequest(