mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-06 16:32:24 +00:00
Android: SVG Images not rendering the first time a view is displayed
Since SVG images are loaded asynchronously, we need to invalidate the view after drawing to the canvas so that the view will be re-rendered. Storing a reference to the actual SvgView in ViewManager, and passing a reference to the ViewManager to each shadow node, so that shadow nodes can invalidate the root SVG view.
This commit is contained in:
@@ -16,12 +16,10 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
|
||||||
import com.facebook.common.executors.UiThreadImmediateExecutorService;
|
import com.facebook.common.executors.UiThreadImmediateExecutorService;
|
||||||
import com.facebook.common.logging.FLog;
|
import com.facebook.common.logging.FLog;
|
||||||
import com.facebook.common.references.CloseableReference;
|
import com.facebook.common.references.CloseableReference;
|
||||||
import com.facebook.datasource.DataSource;
|
import com.facebook.datasource.DataSource;
|
||||||
import com.facebook.common.logging.FLog;
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
|
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
|
||||||
import com.facebook.imagepipeline.image.CloseableBitmap;
|
import com.facebook.imagepipeline.image.CloseableBitmap;
|
||||||
@@ -31,6 +29,9 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
|||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.common.ReactConstants;
|
import com.facebook.react.common.ReactConstants;
|
||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
private String mW;
|
private String mW;
|
||||||
private String mH;
|
private String mH;
|
||||||
private Uri mUri;
|
private Uri mUri;
|
||||||
private boolean mLoading;
|
private AtomicBoolean mLoading = new AtomicBoolean(false);
|
||||||
|
|
||||||
@ReactProp(name = "x")
|
@ReactProp(name = "x")
|
||||||
public void setX(String x) {
|
public void setX(String x) {
|
||||||
@@ -86,13 +87,14 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(final Canvas canvas, final Paint paint, final float opacity) {
|
public void draw(final Canvas canvas, final Paint paint, final float opacity) {
|
||||||
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build();
|
if (!mLoading.get()) {
|
||||||
final boolean inMemoryCache = Fresco.getImagePipeline().isInBitmapMemoryCache(request);
|
final ImageRequest request = ImageRequestBuilder.newBuilderWithSource(mUri).build();
|
||||||
|
|
||||||
if (inMemoryCache) {
|
if (Fresco.getImagePipeline().isInBitmapMemoryCache(request)) {
|
||||||
tryRender(request, canvas, paint, opacity * mOpacity);
|
tryRender(request, canvas, paint, opacity * mOpacity);
|
||||||
} else if (!mLoading) {
|
} else {
|
||||||
loadBitmap(request, canvas, paint);
|
loadBitmap(request, canvas, paint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +108,10 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
paint.reset();
|
paint.reset();
|
||||||
mLoading = false;
|
mLoading.set(false);
|
||||||
|
|
||||||
getSvgShadowNode().drawChildren(canvas, paint);
|
getSvgShadowNode().drawChildren(canvas, paint);
|
||||||
|
getSvgShadowNode().invalidateView(getRect());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +119,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
public void onFailureImpl(DataSource dataSource) {
|
public void onFailureImpl(DataSource dataSource) {
|
||||||
// No cleanup required here.
|
// No cleanup required here.
|
||||||
// TODO: more details about this failure
|
// TODO: more details about this failure
|
||||||
mLoading = false;
|
mLoading.set(false);
|
||||||
FLog.w(ReactConstants.TAG, dataSource.getFailureCause(), "RNSVG: fetchDecodedImage failed!");
|
FLog.w(ReactConstants.TAG, dataSource.getFailureCause(), "RNSVG: fetchDecodedImage failed!");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,18 +127,25 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doRender(@Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final Bitmap bitmap, final float opacity) {
|
@Nonnull
|
||||||
final int count = saveAndSetupCanvas(canvas);
|
private Rect getRect() {
|
||||||
|
|
||||||
clip(canvas, paint);
|
|
||||||
float x = PropHelper.fromPercentageToFloat(mX, mCanvasWidth, 0, mScale);
|
float x = PropHelper.fromPercentageToFloat(mX, mCanvasWidth, 0, mScale);
|
||||||
float y = PropHelper.fromPercentageToFloat(mY, mCanvasHeight, 0, mScale);
|
float y = PropHelper.fromPercentageToFloat(mY, mCanvasHeight, 0, mScale);
|
||||||
float w = PropHelper.fromPercentageToFloat(mW, mCanvasWidth, 0, mScale);
|
float w = PropHelper.fromPercentageToFloat(mW, mCanvasWidth, 0, mScale);
|
||||||
float h = PropHelper.fromPercentageToFloat(mH, mCanvasHeight, 0, mScale);
|
float h = PropHelper.fromPercentageToFloat(mH, mCanvasHeight, 0, mScale);
|
||||||
|
|
||||||
|
return new Rect((int) x, (int) y, (int) (x + w), (int) (y + h));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doRender(@Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final Bitmap bitmap, final float opacity) {
|
||||||
|
final int count = saveAndSetupCanvas(canvas);
|
||||||
|
|
||||||
|
clip(canvas, paint);
|
||||||
|
|
||||||
Paint alphaPaint = new Paint();
|
Paint alphaPaint = new Paint();
|
||||||
alphaPaint.setAlpha((int) (opacity * 255));
|
alphaPaint.setAlpha((int) (opacity * 255));
|
||||||
|
|
||||||
canvas.drawBitmap(bitmap, null, new Rect((int) x, (int) y, (int) (x + w), (int) (y + h)), alphaPaint);
|
canvas.drawBitmap(bitmap, null, getRect(), alphaPaint);
|
||||||
|
|
||||||
restoreCanvas(canvas, count);
|
restoreCanvas(canvas, count);
|
||||||
markUpdateSeen();
|
markUpdateSeen();
|
||||||
|
|||||||
@@ -10,13 +10,14 @@
|
|||||||
package com.horcrux.svg;
|
package com.horcrux.svg;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.facebook.react.uimanager.ThemedReactContext;
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
import com.facebook.react.uimanager.ViewGroupManager;
|
import com.facebook.react.uimanager.ViewGroupManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewManager for RNSVGSvgView React views. Renders as a {@link RNSVGSvgView} and handles
|
* ViewManager for RNSVGSvgView React views. Renders as a {@link RNSVGSvgView} and handles
|
||||||
* invalidating the native view on shadow view updates happening in the underlying tree.
|
* invalidating the native view on shadow view updates happening in the underlying tree.
|
||||||
@@ -24,6 +25,7 @@ import java.util.ArrayList;
|
|||||||
public class RNSVGSvgViewManager extends ViewGroupManager<RNSVGSvgView> {
|
public class RNSVGSvgViewManager extends ViewGroupManager<RNSVGSvgView> {
|
||||||
|
|
||||||
private static final String REACT_CLASS = "RNSVGSvgView";
|
private static final String REACT_CLASS = "RNSVGSvgView";
|
||||||
|
private RNSVGSvgView svgView = null;
|
||||||
|
|
||||||
// TODO: use an ArrayList to connect RNSVGSvgViewShadowNode with RNSVGSvgView, not sure if there will be a race condition.
|
// TODO: use an ArrayList to connect RNSVGSvgViewShadowNode with RNSVGSvgView, not sure if there will be a race condition.
|
||||||
// TODO: find a better way to replace this
|
// TODO: find a better way to replace this
|
||||||
@@ -34,9 +36,14 @@ public class RNSVGSvgViewManager extends ViewGroupManager<RNSVGSvgView> {
|
|||||||
return REACT_CLASS;
|
return REACT_CLASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public RNSVGSvgView getSvgView() {
|
||||||
|
return this.svgView;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RNSVGSvgViewShadowNode createShadowNodeInstance() {
|
public RNSVGSvgViewShadowNode createShadowNodeInstance() {
|
||||||
RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode();
|
RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode(this);
|
||||||
SvgShadowNodes.add(node);
|
SvgShadowNodes.add(node);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -50,7 +57,8 @@ public class RNSVGSvgViewManager extends ViewGroupManager<RNSVGSvgView> {
|
|||||||
protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) {
|
protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) {
|
||||||
RNSVGSvgViewShadowNode shadowNode = SvgShadowNodes.get(0);
|
RNSVGSvgViewShadowNode shadowNode = SvgShadowNodes.get(0);
|
||||||
SvgShadowNodes.remove(0);
|
SvgShadowNodes.remove(0);
|
||||||
return new RNSVGSvgView(reactContext, shadowNode);
|
this.svgView = new RNSVGSvgView(reactContext, shadowNode);
|
||||||
|
return this.svgView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,15 +13,19 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.util.Log;
|
import android.graphics.Rect;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shadow node for RNSVG virtual tree root - RNSVGSvgView
|
* Shadow node for RNSVG virtual tree root - RNSVGSvgView
|
||||||
*/
|
*/
|
||||||
@@ -32,6 +36,13 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
|
|||||||
private static final Map<String, RNSVGVirtualNode> mDefinedTemplates = new HashMap<>();
|
private static final Map<String, RNSVGVirtualNode> mDefinedTemplates = new HashMap<>();
|
||||||
private static final Map<String, PropHelper.RNSVGBrush> mDefinedBrushes = new HashMap<>();
|
private static final Map<String, PropHelper.RNSVGBrush> mDefinedBrushes = new HashMap<>();
|
||||||
|
|
||||||
|
@Nonnull private final RNSVGSvgViewManager viewManager;
|
||||||
|
|
||||||
|
public RNSVGSvgViewShadowNode(@Nonnull final RNSVGSvgViewManager viewManager) {
|
||||||
|
super();
|
||||||
|
this.viewManager = viewManager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
|
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
|
||||||
super.onCollectExtraUpdates(uiUpdater);
|
super.onCollectExtraUpdates(uiUpdater);
|
||||||
@@ -54,7 +65,7 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
|
|||||||
* Draw all of the child nodes of this root node
|
* Draw all of the child nodes of this root node
|
||||||
*
|
*
|
||||||
* This method is synchronized since
|
* This method is synchronized since
|
||||||
* {@link com.horcrux.svg.RNSVGImageShadowNode#loadImage(ImageRequest, Canvas, Paint)} calls it
|
* {@link com.horcrux.svg.RNSVGImageShadowNode#loadBitmap(ImageRequest, Canvas, Paint)} calls it
|
||||||
* asynchronously after images have loaded and are ready to be drawn.
|
* asynchronously after images have loaded and are ready to be drawn.
|
||||||
*
|
*
|
||||||
* @param canvas
|
* @param canvas
|
||||||
@@ -77,6 +88,16 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void invalidateView(@Nonnull final Rect dirtyRect) {
|
||||||
|
final RNSVGSvgView svgView = this.viewManager.getSvgView();
|
||||||
|
if (svgView != null) {
|
||||||
|
final View rootView = svgView.getRootView();
|
||||||
|
if (rootView != null) {
|
||||||
|
rootView.invalidate(dirtyRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void enableTouchEvents() {
|
public void enableTouchEvents() {
|
||||||
if (!mResponsible) {
|
if (!mResponsible) {
|
||||||
mResponsible = true;
|
mResponsible = true;
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
package com.horcrux.svg;
|
package com.horcrux.svg;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@@ -18,16 +16,16 @@ import android.graphics.Path;
|
|||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
|
||||||
import com.facebook.react.uimanager.ReactShadowNode;
|
import com.facebook.react.uimanager.ReactShadowNode;
|
||||||
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and
|
* Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and
|
||||||
@@ -81,8 +79,7 @@ public abstract class RNSVGVirtualNode extends LayoutShadowNode {
|
|||||||
* @param canvas the canvas to set up
|
* @param canvas the canvas to set up
|
||||||
*/
|
*/
|
||||||
protected final int saveAndSetupCanvas(Canvas canvas) {
|
protected final int saveAndSetupCanvas(Canvas canvas) {
|
||||||
int count = canvas.getSaveCount();
|
final int count = canvas.save();
|
||||||
canvas.save();
|
|
||||||
if (mMatrix != null) {
|
if (mMatrix != null) {
|
||||||
canvas.concat(mMatrix);
|
canvas.concat(mMatrix);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user