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:
parent
dfe5f9f762
commit
760422525e
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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://");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue