Add support for RCTImageView in FlatShadowHierarchyManager

Summary: @public This patch adds basic support for RCTImageView (only 'src', 'tintColor' and 'resizeMode' properties are supported for now), and a concept of AttachDetachListener that is required to support it to FlatUIImplementations.

Reviewed By: sriramramani

Differential Revision: D2564389
This commit is contained in:
Denis Koroskin 2015-12-11 22:44:38 -08:00 committed by Ahmed El-Helw
parent dfe5f9f762
commit 760422525e
14 changed files with 784 additions and 14 deletions

View File

@ -54,6 +54,20 @@ package com.facebook.react.flat;
return this;
}
/**
* Returns a non-frozen shallow copy of AbstractDrawCommand as defined by {@link Object#clone()}.
*/
public final AbstractDrawCommand mutableCopy() {
try {
AbstractDrawCommand copy = (AbstractDrawCommand) super.clone();
copy.mFrozen = false;
return copy;
} catch (CloneNotSupportedException e) {
// should not happen since we implement Cloneable
throw new RuntimeException(e);
}
}
/**
* Returns whether this object was frozen and thus cannot be mutated.
*/

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
/**
* An interface that DrawCommands need to implement into order to receive
* {@link android.view.View#onAttachedToWindow()} and
* {@link android.view.View#onDetachedFromWindow()} events.
*/
/* package */ interface AttachDetachListener {
public static final AttachDetachListener[] EMPTY_ARRAY = new AttachDetachListener[0];
/**
* Called when a DrawCommand is being attached to a visible View hierarchy.
* @param callback a WeakReference to a View that provides invalidate() helper method.
*/
public void onAttached(FlatViewGroup.InvalidateCallback callback);
/**
* Called when a DrawCommand is being detached from a visible View hierarchy.
*/
public void onDetached();
}

View File

@ -0,0 +1,171 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Bitmap;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
/**
* Helper class for DrawImage that helps manage fetch requests through ImagePipeline.
*
* Request states this class can be in:
* 1) mDataSource == null, mImageRef == null : request has not be started, was canceled or failed.
* 2) mDataSource != null, mImageRef == null : request is in progress.
* 3) mDataSource == null, mImageRef != null : request successfully finished.
* 4) mDataSource != null, mImageRef != null : invalid state (should never happen)
*/
/* package */ final class BitmapRequestHelper
implements DataSubscriber<CloseableReference<CloseableImage>> {
private final ImageRequest mImageRequest;
private final DrawImageWithPipeline mDrawImage;
private @Nullable DataSource<CloseableReference<CloseableImage>> mDataSource;
private @Nullable CloseableReference<CloseableImage> mImageRef;
private int mAttachCounter;
/* package */ BitmapRequestHelper(ImageRequest imageRequest, DrawImageWithPipeline drawImage) {
mImageRequest = imageRequest;
mDrawImage = drawImage;
}
/* package */ void attach() {
mAttachCounter++;
if (mAttachCounter != 1) {
// this is a secondary attach, ignore it, only updating Bitmap boundaries if needed.
Bitmap bitmap = getBitmap();
if (bitmap != null) {
mDrawImage.updateBounds(bitmap);
}
return;
}
Assertions.assertCondition(mDataSource == null);
Assertions.assertCondition(mImageRef == null);
// Submit the request
ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline();
mDataSource = imagePipeline.fetchDecodedImage(mImageRequest, RCTImageView.getCallerContext());
mDataSource.subscribe(this, UiThreadImmediateExecutorService.getInstance());
}
/**
* Returns whether detach() was primary, false otherwise.
*/
/* package */ void detach() {
--mAttachCounter;
if (mAttachCounter != 0) {
// this is a secondary detach, ignore it
return;
}
if (mDataSource != null) {
mDataSource.close();
mDataSource = null;
}
if (mImageRef != null) {
mImageRef.close();
mImageRef = null;
}
}
/**
* Returns an unsafe bitmap reference. Do not assign the result of this method to anything other
* than a local variable, or it will no longer work with the reference count goes to zero.
*/
/* package */ @Nullable Bitmap getBitmap() {
if (mImageRef == null) {
return null;
}
CloseableImage closeableImage = mImageRef.get();
if (!(closeableImage instanceof CloseableBitmap)) {
mImageRef.close();
mImageRef = null;
return null;
}
return ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
}
@Override
public void onNewResult(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (!dataSource.isFinished()) {
// only interested in final image, no need to close the dataSource
return;
}
try {
if (mDataSource != dataSource) {
// Shouldn't ever happen, but let's be safe (dataSource got closed by callback still fired?)
return;
}
mDataSource = null;
CloseableReference<CloseableImage> imageReference = dataSource.getResult();
if (imageReference == null) {
// Shouldn't ever happen, but let's be safe (dataSource got closed by callback still fired?)
return;
}
CloseableImage image = imageReference.get();
if (!(image instanceof CloseableBitmap)) {
// only bitmaps are supported
imageReference.close();
return;
}
mImageRef = imageReference;
Bitmap bitmap = getBitmap();
if (bitmap == null) {
// Shouldn't ever happen, but let's be safe.
return;
}
// now that we have the Bitmap, DrawImage can finally initialize its
// tranformation matrix to satisfy requested ScaleType.
mDrawImage.updateBounds(bitmap);
} finally {
dataSource.close();
}
}
@Override
public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (mDataSource != dataSource) {
// Should always be the case, but let's be safe.
mDataSource = null;
}
dataSource.close();
}
@Override
public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) {
}
@Override
public void onProgressUpdate(DataSource<CloseableReference<CloseableImage>> dataSource) {
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.imagepipeline.request.ImageRequest;
/**
* Common interface for DrawImageWithPipeline and DrawImageWithDrawee.
*/
/* package */ interface DrawImage extends DrawCommand, AttachDetachListener {
/**
* Returns true if an image source was assigned to the DrawImage.
* A DrawImage with no source will not draw anything.
*/
public boolean hasImageRequest();
/**
* Assigns a new image request to the DrawImage, or null to clear the image request.
*/
public void setImageRequest(@Nullable ImageRequest imageRequest);
/**
* Assigns a tint color to apply to the image drawn.
*/
public void setTintColor(int tintColor);
/**
* Assigns a scale type to draw to the image with.
*/
public void setScaleType(ScaleType scaleType);
/**
* Returns a scale type to draw to the image with.
*/
public ScaleType getScaleType();
}

View File

@ -0,0 +1,144 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.views.image.ImageResizeMode;
/**
* DrawImageWithPipeline is DrawCommand that can draw a local or remote image.
* It uses BitmapRequestHelper internally to fetch and cache the images.
*/
/* package */ final class DrawImageWithPipeline extends AbstractDrawCommand implements DrawImage {
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private final Matrix mTransform = new Matrix();
private ScaleType mScaleType = ImageResizeMode.defaultValue();
private @Nullable BitmapRequestHelper mBitmapRequestHelper;
private @Nullable PorterDuffColorFilter mColorFilter;
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
private boolean mForceClip;
@Override
public boolean hasImageRequest() {
return mBitmapRequestHelper != null;
}
@Override
public void setImageRequest(@Nullable ImageRequest imageRequest) {
if (imageRequest == null) {
mBitmapRequestHelper = null;
} else {
mBitmapRequestHelper = new BitmapRequestHelper(imageRequest, this);
}
}
@Override
public void setTintColor(int tintColor) {
if (tintColor == 0) {
mColorFilter = null;
} else {
mColorFilter = new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
}
}
@Override
public void setScaleType(ScaleType scaleType) {
mScaleType = scaleType;
}
@Override
public ScaleType getScaleType() {
return mScaleType;
}
@Override
public void draw(Canvas canvas) {
Bitmap bitmap = Assertions.assumeNotNull(mBitmapRequestHelper).getBitmap();
if (bitmap == null) {
return;
}
PAINT.setColorFilter(mColorFilter);
if (mForceClip) {
canvas.save();
canvas.clipRect(getLeft(), getTop(), getRight(), getBottom());
canvas.drawBitmap(bitmap, mTransform, PAINT);
canvas.restore();
} else {
canvas.drawBitmap(bitmap, mTransform, PAINT);
}
}
@Override
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
mCallback = callback;
Assertions.assumeNotNull(mBitmapRequestHelper).attach();
}
@Override
public void onDetached() {
Assertions.assumeNotNull(mBitmapRequestHelper).detach();
}
/* package */ void updateBounds(Bitmap bitmap) {
Assertions.assumeNotNull(mCallback).invalidate();
float left = getLeft();
float top = getTop();
float containerWidth = getRight() - left;
float containerHeight = getBottom() - top;
float imageWidth = (float) bitmap.getWidth();
float imageHeight = (float) bitmap.getHeight();
mForceClip = false;
if (mScaleType == ScaleType.FIT_XY) {
mTransform.setScale(containerWidth / imageWidth, containerHeight / imageHeight);
mTransform.postTranslate(left, top);
return;
}
final float scale;
if (mScaleType == ScaleType.CENTER_INSIDE) {
final float ratio;
if (containerWidth >= imageWidth && containerHeight >= imageHeight) {
scale = 1.0f;
} else {
scale = Math.min(containerWidth / imageWidth, containerHeight / imageHeight);
}
} else {
scale = Math.max(containerWidth / imageWidth, containerHeight / imageHeight);
}
float paddingLeft = (containerWidth - imageWidth * scale) / 2;
float paddingTop = (containerHeight - imageHeight * scale) / 2;
mForceClip = paddingLeft < 0 || paddingTop < 0;
mTransform.setScale(scale, scale);
mTransform.postTranslate(left + paddingLeft, top + paddingTop);
}
}

View File

@ -9,6 +9,8 @@
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.view.View;
import android.view.View.MeasureSpec;
@ -39,14 +41,23 @@ import com.facebook.react.uimanager.ViewManagerRegistry;
}
/**
* Assigns new DrawCommands to a FlatViewGroup specified by a reactTag.
* Updates DrawCommands and AttachDetachListeners of a FlatViewGroup specified by a reactTag.
*
* @param reactTag reactTag to lookup FlatViewGroup by
* @param drawCommands new draw commands to execute during the drawing.
* @param drawCommands if non-null, new draw commands to execute during the drawing.
* @param listeners if non-null, new attach-detach listeners.
*/
/* package */ void updateMountState(int reactTag, DrawCommand[] drawCommands) {
/* package */ void updateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners) {
FlatViewGroup view = (FlatViewGroup) resolveView(reactTag);
view.mountDrawCommands(drawCommands);
if (drawCommands != null) {
view.mountDrawCommands(drawCommands);
}
if (listeners != null) {
view.mountAttachDetachListeners(listeners);
}
}
/**

View File

@ -15,11 +15,13 @@ package com.facebook.react.flat;
/* package */ final class FlatRootShadowNode extends FlatShadowNode {
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
private int mViewLeft;
private int mViewTop;
private int mViewRight;
private int mViewBottom;
private boolean mIsUpdated;
@Override
public int getScreenX() {
@ -41,6 +43,30 @@ package com.facebook.react.flat;
return mViewBottom - mViewTop;
}
/**
* Returns true when this CSSNode tree needs to be re-laid out. If true, FlatUIImplementation
* will request LayoutEngine to perform a layout pass to update node boundaries. This is used
* to avoid unnecessary node updates.
*/
/* package */ boolean needsLayout() {
return isDirty();
}
/**
* Returns true if there are updates to the node tree other than layout (such as a change in
* background color) that would require StateBuilder to re-collect drawing state.
*/
/* package */ boolean isUpdated() {
return mIsUpdated;
}
/**
* Marks the node tree as requiring or not requiring a StateBuilder pass to collect drawing state.
*/
/* package */ void markUpdated(boolean isUpdated) {
mIsUpdated = isUpdated;
}
/**
* Returns an array of DrawCommands to perform during the View's draw pass.
*/
@ -56,6 +82,21 @@ package com.facebook.react.flat;
mDrawCommands = drawCommands;
}
/**
* Sets an array of AttachDetachListeners to call onAttach/onDetach when they are attached to or
* detached from a View that this shadow node maps to.
*/
/* package */ void setAttachDetachListeners(AttachDetachListener[] listeners) {
mAttachDetachListeners = listeners;
}
/**
* Returns an array of AttachDetachListeners associated with this shadow node.
*/
/* package */ AttachDetachListener[] getAttachDetachListeners() {
return mAttachDetachListeners;
}
/**
* Sets boundaries of the View that this node maps to relative to the parent left/top coordinate.
*/

View File

@ -28,4 +28,15 @@ import com.facebook.react.uimanager.LayoutShadowNode;
float bottom) {
// do nothing yet.
}
/**
* Marks root node as updated to trigger a StateBuilder pass to collect DrawCommands for the node
* tree. Use it when FlatShadowNode is updated but doesn't require a layout pass (e.g. background
* color is changed).
*/
protected final void invalidate() {
// getRootNode() returns an ReactShadowNode, which is guarantied to be a FlatRootShadowNode.
FlatRootShadowNode rootNode = (FlatRootShadowNode) getRootNode();
rootNode.markUpdated(true);
}
}

View File

@ -22,6 +22,7 @@ import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.image.ReactImageManager;
/**
* FlatUIImplementation builds on top of UIImplementation and allows pre-creating everything
@ -36,12 +37,22 @@ public class FlatUIImplementation extends UIImplementation {
ReactApplicationContext reactContext,
List<ViewManager> viewManagers) {
ReactImageManager reactImageManager = findReactImageManager(viewManagers);
if (reactImageManager != null) {
Object callerContext = reactImageManager.getCallerContext();
if (callerContext != null) {
RCTImageView.setCallerContext(callerContext);
}
}
TypefaceCache.setAssetManager(reactContext.getAssets());
viewManagers = new ArrayList<ViewManager>(viewManagers);
viewManagers.add(new RCTViewManager());
viewManagers.add(new RCTTextManager());
viewManagers.add(new RCTRawTextManager());
viewManagers.add(new RCTVirtualTextManager());
TypefaceCache.setAssetManager(reactContext.getAssets());
viewManagers.add(new RCTImageViewManager());
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers);
FlatNativeViewHierarchyManager nativeViewHierarchyManager = new FlatNativeViewHierarchyManager(
@ -131,12 +142,33 @@ public class FlatUIImplementation extends UIImplementation {
}
}
@Override
protected void calculateRootLayout(ReactShadowNode cssRoot) {
}
@Override
protected void applyUpdatesRecursive(
ReactShadowNode cssNode,
float absoluteX,
float absoluteY,
EventDispatcher eventDispatcher) {
mStateBuilder.applyUpdates((FlatRootShadowNode) cssNode);
FlatRootShadowNode rootNode = (FlatRootShadowNode) cssNode;
if (!rootNode.needsLayout() && !rootNode.isUpdated()) {
return;
}
super.calculateRootLayout(rootNode);
rootNode.markUpdated(false);
mStateBuilder.applyUpdates(rootNode);
}
private static @Nullable ReactImageManager findReactImageManager(List<ViewManager> viewManagers) {
for (int i = 0, size = viewManagers.size(); i != size; ++i) {
if (viewManagers.get(i) instanceof ReactImageManager) {
return (ReactImageManager) viewManagers.get(i);
}
}
return null;
}
}

View File

@ -9,6 +9,8 @@
package com.facebook.react.flat;
import javax.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIViewOperationQueue;
@ -26,16 +28,24 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
private final class UpdateMountState implements UIOperation {
private final int mReactTag;
private final DrawCommand[] mDrawCommands;
private final @Nullable DrawCommand[] mDrawCommands;
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
private UpdateMountState(int reactTag, DrawCommand[] drawCommands) {
private UpdateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners) {
mReactTag = reactTag;
mDrawCommands = drawCommands;
mAttachDetachListeners = listeners;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateMountState(mReactTag, mDrawCommands);
mNativeViewHierarchyManager.updateMountState(
mReactTag,
mDrawCommands,
mAttachDetachListeners);
}
}
@ -75,8 +85,11 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
/**
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
*/
public void enqueueUpdateMountState(int reactTag, DrawCommand[] drawCommands) {
enqueueUIOperation(new UpdateMountState(reactTag, drawCommands));
public void enqueueUpdateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners) {
enqueueUIOperation(new UpdateMountState(reactTag, drawCommands, listeners));
}
/**

View File

@ -9,6 +9,10 @@
package com.facebook.react.flat;
import java.lang.ref.WeakReference;
import javax.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.view.ViewGroup;
@ -18,8 +22,30 @@ import android.view.ViewGroup;
* array of DrawCommands, executing them one by one.
*/
/* package */ final class FlatViewGroup extends ViewGroup {
/**
* Helper class that allows AttachDetachListener to invalidate the hosting View.
*/
static final class InvalidateCallback extends WeakReference<FlatViewGroup> {
private InvalidateCallback(FlatViewGroup view) {
super(view);
}
/**
* Propagates invalidate() call up to the hosting View (if it's still alive)
*/
public void invalidate() {
FlatViewGroup view = get();
if (view != null) {
view.invalidate();
}
}
}
private @Nullable InvalidateCallback mInvalidateCallback;
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
private boolean mIsAttached = false;
/* package */ FlatViewGroup(Context context) {
super(context);
@ -39,8 +65,78 @@ import android.view.ViewGroup;
// nothing to do here
}
@Override
protected void onAttachedToWindow() {
if (mIsAttached) {
// this is possible, unfortunately.
return;
}
mIsAttached = true;
super.onAttachedToWindow();
dispatchOnAttached(mAttachDetachListeners);
}
@Override
protected void onDetachedFromWindow() {
if (!mIsAttached) {
throw new RuntimeException("Double detach");
}
mIsAttached = false;
super.onDetachedFromWindow();
dispatchOnDetached(mAttachDetachListeners);
}
/* package */ void mountDrawCommands(DrawCommand[] drawCommands) {
mDrawCommands = drawCommands;
invalidate();
}
/* package */ void mountAttachDetachListeners(AttachDetachListener[] listeners) {
if (mIsAttached) {
// Ordering of the following 2 statements is very important. While logically it makes sense to
// detach old listeners first, and only then attach new listeners, this is not very efficient,
// because a listener can be in both lists. In this case, it will be detached first and then
// re-attached immediately. This is undesirable for a couple of reasons:
// 1) performance. Detaching is slow because it may cancel an ongoing network request
// 2) it may cause flicker: an image that was already loaded may get unloaded.
//
// For this reason, we are attaching new listeners first. What this means is that listeners
// that are in both lists need to gracefully handle a secondary attach and detach events,
// (i.e. onAttach() being called when already attached, followed by a detach that should be
// ignored) turning them into no-ops. This will result in no performance loss and no flicker,
// because ongoing network requests don't get cancelled.
dispatchOnAttached(listeners);
dispatchOnDetached(mAttachDetachListeners);
}
mAttachDetachListeners = listeners;
}
private void dispatchOnAttached(AttachDetachListener[] listeners) {
int numListeners = listeners.length;
if (numListeners == 0) {
return;
}
InvalidateCallback callback = getInvalidateCallback();
for (AttachDetachListener listener : listeners) {
listener.onAttached(callback);
}
}
private InvalidateCallback getInvalidateCallback() {
if (mInvalidateCallback == null) {
mInvalidateCallback = new InvalidateCallback(this);
}
return mInvalidateCallback;
}
private static void dispatchOnDetached(AttachDetachListener[] listeners) {
for (AttachDetachListener listener : listeners) {
listener.onDetached();
}
}
}

View File

@ -0,0 +1,117 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.uimanager.ReactProp;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.views.image.ImageResizeMode;
/**
* RCTImageView is a top-level node for Image. It can display either a remote image
* (source must start wtih http:// or https://) or a local resource (a BitmapDrawable).
*/
/* package */ class RCTImageView<T extends AbstractDrawCommand & DrawImage> extends FlatShadowNode {
static Object sCallerContext = RCTImageView.class;
/**
* Assignes a CallerContext to execute network requests with.
*/
/* package */ static void setCallerContext(Object callerContext) {
sCallerContext = callerContext;
}
/* package */ static Object getCallerContext() {
return sCallerContext;
}
private T mDrawImage;
/* package */ RCTImageView(T drawImage) {
mDrawImage = drawImage;
}
@Override
protected void collectState(
StateBuilder stateBuilder,
float left,
float top,
float right,
float bottom) {
super.collectState(stateBuilder, left, top, right, bottom);
if (mDrawImage.hasImageRequest()) {
mDrawImage = (T) mDrawImage.updateBoundsAndFreeze(
left,
top,
right,
bottom);
stateBuilder.addDrawCommand(mDrawImage);
stateBuilder.addAttachDetachListener(mDrawImage);
}
}
@ReactProp(name = "src")
public void setSource(@Nullable String source) {
if (source == null) {
getMutableDrawImage().setImageRequest(null);
return;
}
final ImageRequestBuilder imageRequestBuilder;
if (isNetworkResource(source)) {
imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(source));
} else {
Context context = getThemedContext();
Resources resources = context.getResources();
int resId = resources.getIdentifier(
source,
"drawable",
context.getPackageName());
imageRequestBuilder = ImageRequestBuilder.newBuilderWithResourceId(resId);
}
getMutableDrawImage().setImageRequest(imageRequestBuilder.build());
}
@ReactProp(name = "tintColor")
public void setTintColor(int tintColor) {
getMutableDrawImage().setTintColor(tintColor);
}
@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(@Nullable String resizeMode) {
ScaleType scaleType = ImageResizeMode.toScaleType(resizeMode);
if (mDrawImage.getScaleType() != scaleType) {
getMutableDrawImage().setScaleType(scaleType);
}
}
private T getMutableDrawImage() {
if (mDrawImage.isFrozen()) {
mDrawImage = (T) mDrawImage.mutableCopy();
invalidate();
}
return mDrawImage;
}
private static boolean isNetworkResource(String source) {
return source.startsWith("http://") || source.startsWith("https://");
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
/* package */ final class RCTImageViewManager extends VirtualViewManager<RCTImageView> {
@Override
public String getName() {
return "RCTImageView";
}
@Override
public RCTImageView createShadowNodeInstance() {
return new RCTImageView(new DrawImageWithPipeline());
}
@Override
public Class<RCTImageView> getShadowNodeClass() {
return RCTImageView.class;
}
}

View File

@ -20,7 +20,9 @@ package com.facebook.react.flat;
private final FlatUIViewOperationQueue mOperationsQueue;
private final ElementsList<DrawCommand> mDrawCommands =
new ElementsList(DrawCommand.EMPTY_ARRAY);
new ElementsList<>(DrawCommand.EMPTY_ARRAY);
private final ElementsList<AttachDetachListener> mAttachDetachListeners =
new ElementsList<>(AttachDetachListener.EMPTY_ARRAY);
/* package */ StateBuilder(FlatUIViewOperationQueue operationsQueue) {
mOperationsQueue = operationsQueue;
@ -41,6 +43,10 @@ package com.facebook.react.flat;
mDrawCommands.add(drawCommand);
}
/* package */ void addAttachDetachListener(AttachDetachListener listener) {
mAttachDetachListeners.add(listener);
}
/**
* Updates boundaries of a View that a give nodes maps to.
*/
@ -76,15 +82,25 @@ package com.facebook.react.flat;
float width,
float height) {
mDrawCommands.start(node.getDrawCommands());
mAttachDetachListeners.start(node.getAttachDetachListeners());
collectStateRecursively(node, 0, 0, width, height);
boolean shouldUpdateMountState = false;
final DrawCommand[] drawCommands = mDrawCommands.finish();
if (drawCommands != null) {
// DrawCommands changed, need to re-mount them and re-draw the View.
shouldUpdateMountState = true;
node.setDrawCommands(drawCommands);
}
mOperationsQueue.enqueueUpdateMountState(tag, drawCommands);
final AttachDetachListener[] listeners = mAttachDetachListeners.finish();
if (listeners != null) {
shouldUpdateMountState = true;
node.setAttachDetachListeners(listeners);
}
if (shouldUpdateMountState) {
mOperationsQueue.enqueueUpdateMountState(tag, drawCommands, listeners);
}
}