Remove implementation of Nodes (react/flat package)
Summary: This diff removes the Nodes implementation from the source code. Why I'm deleting this code: - I enabled nodes in catalyst app and it doesn't render even the first screen (see error images) - This code is not being used at all inside facebook - The last relevant commit I found was done in April 2017. - Deleting this code make it easy to do refactors, as we don't have to modify this code. When rendering an Image: {F131075074} When clicking on "RELOAD": {F131075075} Based on these errors I assume that this is not being used in Open Source. CC @[121800083:hramos] Reviewed By: hramos Differential Revision: D8640978 fbshipit-source-id: 243ee6440ebdbd24fd9f3daadc1066fd8487d9cc
This commit is contained in:
parent
c972a5c298
commit
a373bf705d
|
@ -1,130 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathEffect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* Base class for border drawing operations (used by DrawImage and DrawBorder). Draws rectangular or
|
||||
* rounded border along the rectangle, defined by the bounding box of the AbstractDrawCommand.
|
||||
*/
|
||||
/* package */ abstract class AbstractDrawBorder extends AbstractDrawCommand {
|
||||
|
||||
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private static final RectF TMP_RECT = new RectF();
|
||||
private static final int BORDER_PATH_DIRTY = 1 << 0;
|
||||
|
||||
static {
|
||||
PAINT.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
private int mSetPropertiesFlag;
|
||||
private int mBorderColor = Color.BLACK;
|
||||
private float mBorderWidth;
|
||||
private float mBorderRadius;
|
||||
private @Nullable Path mPathForBorderRadius;
|
||||
|
||||
public final void setBorderWidth(float borderWidth) {
|
||||
mBorderWidth = borderWidth;
|
||||
setFlag(BORDER_PATH_DIRTY);
|
||||
}
|
||||
|
||||
public final float getBorderWidth() {
|
||||
return mBorderWidth;
|
||||
}
|
||||
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
mBorderRadius = borderRadius;
|
||||
setFlag(BORDER_PATH_DIRTY);
|
||||
}
|
||||
|
||||
public final float getBorderRadius() {
|
||||
return mBorderRadius;
|
||||
}
|
||||
|
||||
public final void setBorderColor(int borderColor) {
|
||||
mBorderColor = borderColor;
|
||||
}
|
||||
|
||||
public final int getBorderColor() {
|
||||
return mBorderColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChanged() {
|
||||
setFlag(BORDER_PATH_DIRTY);
|
||||
}
|
||||
|
||||
protected final void drawBorders(Canvas canvas) {
|
||||
if (mBorderWidth < 0.5f) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBorderColor == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PAINT.setColor(mBorderColor);
|
||||
PAINT.setStrokeWidth(mBorderWidth);
|
||||
PAINT.setPathEffect(getPathEffectForBorderStyle());
|
||||
canvas.drawPath(getPathForBorderRadius(), PAINT);
|
||||
}
|
||||
|
||||
protected final void updatePath(Path path, float correction) {
|
||||
path.reset();
|
||||
|
||||
TMP_RECT.set(
|
||||
getLeft() + correction,
|
||||
getTop() + correction,
|
||||
getRight() - correction,
|
||||
getBottom() - correction);
|
||||
|
||||
path.addRoundRect(
|
||||
TMP_RECT,
|
||||
mBorderRadius,
|
||||
mBorderRadius,
|
||||
Path.Direction.CW);
|
||||
}
|
||||
|
||||
protected @Nullable PathEffect getPathEffectForBorderStyle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final boolean isFlagSet(int mask) {
|
||||
return (mSetPropertiesFlag & mask) == mask;
|
||||
}
|
||||
|
||||
protected final void setFlag(int mask) {
|
||||
mSetPropertiesFlag |= mask;
|
||||
}
|
||||
|
||||
protected final void resetFlag(int mask) {
|
||||
mSetPropertiesFlag &= ~mask;
|
||||
}
|
||||
|
||||
protected final Path getPathForBorderRadius() {
|
||||
if (isFlagSet(BORDER_PATH_DIRTY)) {
|
||||
if (mPathForBorderRadius == null) {
|
||||
mPathForBorderRadius = new Path();
|
||||
}
|
||||
|
||||
updatePath(mPathForBorderRadius, mBorderWidth * 0.5f);
|
||||
|
||||
resetFlag(BORDER_PATH_DIRTY);
|
||||
}
|
||||
|
||||
return mPathForBorderRadius;
|
||||
}
|
||||
}
|
|
@ -1,299 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
/**
|
||||
* Base class for all DrawCommands. Becomes immutable once it has its bounds set. Until then, a
|
||||
* subclass is able to mutate any of its properties (e.g. updating Layout in DrawTextLayout).
|
||||
*
|
||||
* The idea is to be able to reuse unmodified objects when we build up DrawCommands before we ship
|
||||
* them to UI thread, but we can only do that if DrawCommands are immutable.
|
||||
*/
|
||||
/* package */ abstract class AbstractDrawCommand extends DrawCommand implements Cloneable {
|
||||
|
||||
private float mLeft;
|
||||
private float mTop;
|
||||
private float mRight;
|
||||
private float mBottom;
|
||||
private boolean mFrozen;
|
||||
|
||||
protected boolean mNeedsClipping;
|
||||
private float mClipLeft;
|
||||
private float mClipTop;
|
||||
private float mClipRight;
|
||||
private float mClipBottom;
|
||||
|
||||
// Used to draw highlights in debug draw.
|
||||
private static Paint sDebugHighlightRed;
|
||||
private static Paint sDebugHighlightYellow;
|
||||
private static Paint sDebugHighlightOverlayText;
|
||||
|
||||
public final boolean clipBoundsMatch(
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
return mClipLeft == clipLeft && mClipTop == clipTop
|
||||
&& mClipRight == clipRight && mClipBottom == clipBottom;
|
||||
}
|
||||
|
||||
protected final void setClipBounds(
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
mClipLeft = clipLeft;
|
||||
mClipTop = clipTop;
|
||||
mClipRight = clipRight;
|
||||
mClipBottom = clipBottom;
|
||||
// We put this check here to not clip when we have the default [-infinity, infinity] bounds,
|
||||
// since clipRect in those cases is essentially no-op anyway. This is needed to fix a bug that
|
||||
// shows up during screenshot testing. Note that checking one side is enough, since if one side
|
||||
// is infinite, all sides will be infinite, since we only set infinite for all sides at the
|
||||
// same time - conversely, if one side is finite, all sides will be finite.
|
||||
mNeedsClipping = mClipLeft != Float.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
public final float getClipLeft() {
|
||||
return mClipLeft;
|
||||
}
|
||||
|
||||
public final float getClipTop() {
|
||||
return mClipTop;
|
||||
}
|
||||
|
||||
public final float getClipRight() {
|
||||
return mClipRight;
|
||||
}
|
||||
|
||||
public final float getClipBottom() {
|
||||
return mClipBottom;
|
||||
}
|
||||
|
||||
protected void applyClipping(Canvas canvas) {
|
||||
canvas.clipRect(mClipLeft, mClipTop, mClipRight, mClipBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't override this unless you need to do custom clipping in a draw command. Otherwise just
|
||||
* override onPreDraw and onDraw.
|
||||
*/
|
||||
@Override
|
||||
public void draw(FlatViewGroup parent, Canvas canvas) {
|
||||
onPreDraw(parent, canvas);
|
||||
if (mNeedsClipping && shouldClip()) {
|
||||
canvas.save(Canvas.CLIP_SAVE_FLAG);
|
||||
applyClipping(canvas);
|
||||
onDraw(canvas);
|
||||
canvas.restore();
|
||||
} else {
|
||||
onDraw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int getDebugBorderColor() {
|
||||
return Color.CYAN;
|
||||
}
|
||||
|
||||
protected String getDebugName() {
|
||||
return getClass().getSimpleName().substring(4);
|
||||
}
|
||||
|
||||
private void initDebugHighlightResources(FlatViewGroup parent) {
|
||||
if (sDebugHighlightRed == null) {
|
||||
sDebugHighlightRed = new Paint();
|
||||
sDebugHighlightRed.setARGB(75, 255, 0, 0);
|
||||
}
|
||||
if (sDebugHighlightYellow == null) {
|
||||
sDebugHighlightYellow = new Paint();
|
||||
sDebugHighlightYellow.setARGB(100, 255, 204, 0);
|
||||
}
|
||||
if (sDebugHighlightOverlayText == null) {
|
||||
sDebugHighlightOverlayText = new Paint();
|
||||
sDebugHighlightOverlayText.setAntiAlias(true);
|
||||
sDebugHighlightOverlayText.setARGB(200, 50, 50, 50);
|
||||
sDebugHighlightOverlayText.setTextAlign(Paint.Align.RIGHT);
|
||||
sDebugHighlightOverlayText.setTypeface(Typeface.MONOSPACE);
|
||||
sDebugHighlightOverlayText.setTextSize(parent.dipsToPixels(9));
|
||||
}
|
||||
}
|
||||
|
||||
private void debugDrawHighlightRect(Canvas canvas, Paint paint, String text) {
|
||||
canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), paint);
|
||||
canvas.drawText(text, getRight() - 5, getBottom() - 5, sDebugHighlightOverlayText);
|
||||
}
|
||||
|
||||
protected void debugDrawWarningHighlight(Canvas canvas, String text) {
|
||||
debugDrawHighlightRect(canvas, sDebugHighlightRed, text);
|
||||
}
|
||||
|
||||
protected void debugDrawCautionHighlight(Canvas canvas, String text) {
|
||||
debugDrawHighlightRect(canvas, sDebugHighlightYellow, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void debugDraw(FlatViewGroup parent, Canvas canvas) {
|
||||
onDebugDraw(parent, canvas);
|
||||
if (FlatViewGroup.DEBUG_HIGHLIGHT_PERFORMANCE_ISSUES) {
|
||||
initDebugHighlightResources(parent);
|
||||
onDebugDrawHighlight(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDebugDraw(FlatViewGroup parent, Canvas canvas) {
|
||||
parent.debugDrawNamedRect(
|
||||
canvas,
|
||||
getDebugBorderColor(),
|
||||
getDebugName(),
|
||||
mLeft,
|
||||
mTop,
|
||||
mRight,
|
||||
mBottom);
|
||||
}
|
||||
|
||||
protected void onDebugDrawHighlight(Canvas canvas) {
|
||||
}
|
||||
|
||||
protected void onPreDraw(FlatViewGroup parent, Canvas canvas) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates boundaries of the AbstractDrawCommand and freezes it.
|
||||
* Will return a frozen copy if the current AbstractDrawCommand cannot be mutated.
|
||||
*
|
||||
* This should not be called on a DrawView, as the DrawView is modified on UI thread. Use
|
||||
* DrawView.collectDrawView instead to avoid race conditions.
|
||||
*/
|
||||
public AbstractDrawCommand updateBoundsAndFreeze(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
if (mFrozen) {
|
||||
// see if we can reuse it
|
||||
boolean boundsMatch = boundsMatch(left, top, right, bottom);
|
||||
boolean clipBoundsMatch = clipBoundsMatch(clipLeft, clipTop, clipRight, clipBottom);
|
||||
if (boundsMatch && clipBoundsMatch) {
|
||||
return this;
|
||||
}
|
||||
|
||||
try {
|
||||
AbstractDrawCommand copy = (AbstractDrawCommand) clone();
|
||||
if (!boundsMatch) {
|
||||
copy.setBounds(left, top, right, bottom);
|
||||
}
|
||||
if (!clipBoundsMatch) {
|
||||
copy.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
|
||||
}
|
||||
return copy;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// This should not happen since AbstractDrawCommand implements Cloneable
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
setBounds(left, top, right, bottom);
|
||||
setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
|
||||
mFrozen = true;
|
||||
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.
|
||||
*/
|
||||
public final boolean isFrozen() {
|
||||
return mFrozen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this object as frozen, indicating that it should not be mutated.
|
||||
*/
|
||||
public final void freeze() {
|
||||
mFrozen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Left position of this DrawCommand relative to the hosting View.
|
||||
*/
|
||||
public final float getLeft() {
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top position of this DrawCommand relative to the hosting View.
|
||||
*/
|
||||
public final float getTop() {
|
||||
return mTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right position of this DrawCommand relative to the hosting View.
|
||||
*/
|
||||
public final float getRight() {
|
||||
return mRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom position of this DrawCommand relative to the hosting View.
|
||||
*/
|
||||
public final float getBottom() {
|
||||
return mBottom;
|
||||
}
|
||||
|
||||
protected abstract void onDraw(Canvas canvas);
|
||||
|
||||
protected boolean shouldClip() {
|
||||
return mLeft < getClipLeft() || mTop < getClipTop() ||
|
||||
mRight > getClipRight() || mBottom > getClipBottom();
|
||||
}
|
||||
|
||||
protected void onBoundsChanged() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates boundaries of this DrawCommand.
|
||||
*/
|
||||
protected final void setBounds(float left, float top, float right, float bottom) {
|
||||
mLeft = left;
|
||||
mTop = top;
|
||||
mRight = right;
|
||||
mBottom = bottom;
|
||||
|
||||
onBoundsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if boundaries match and don't need to be updated. False otherwise.
|
||||
*/
|
||||
protected final boolean boundsMatch(float left, float top, float right, float bottom) {
|
||||
return mLeft == left && mTop == top && mRight == right && mBottom == bottom;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
interface AndroidView {
|
||||
|
||||
/**
|
||||
* Whether or not custom layout is needed for the children
|
||||
* @return a boolean representing whether custom layout is needed
|
||||
*/
|
||||
boolean needsCustomLayoutForChildren();
|
||||
|
||||
/**
|
||||
* Did the padding change
|
||||
* @return a boolean representing whether the padding changed
|
||||
*/
|
||||
boolean isPaddingChanged();
|
||||
|
||||
/**
|
||||
* Reset the padding changed internal state
|
||||
*/
|
||||
void resetPaddingChanged();
|
||||
|
||||
/**
|
||||
* Get the padding for a certain spacingType defined in com.facebook.yoga.Spacing
|
||||
*/
|
||||
float getPadding(int spacingType);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
load("//ReactNative:DEFS.bzl", "YOGA_TARGET", "react_native_dep", "react_native_target", "rn_android_library")
|
||||
|
||||
rn_android_library(
|
||||
name = "flat",
|
||||
srcs = glob(["*.java"]),
|
||||
provided_deps = [
|
||||
react_native_dep("third-party/android/support/v4:lib-support-v4"),
|
||||
],
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
YOGA_TARGET,
|
||||
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
|
||||
react_native_dep("libraries/fresco/fresco-react-native:fbcore"),
|
||||
react_native_dep("libraries/fresco/fresco-react-native:fresco-drawee"),
|
||||
react_native_dep("libraries/fresco/fresco-react-native:fresco-react-native"),
|
||||
react_native_dep("libraries/fresco/fresco-react-native:imagepipeline"),
|
||||
react_native_dep("libraries/textlayoutbuilder:textlayoutbuilder"),
|
||||
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/modules/fresco:fresco"),
|
||||
react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"),
|
||||
react_native_target("java/com/facebook/react/touch:touch"),
|
||||
react_native_target("java/com/facebook/react/uimanager:uimanager"),
|
||||
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
|
||||
react_native_target("java/com/facebook/react/views/art:art"),
|
||||
react_native_target("java/com/facebook/react/views/image:image"),
|
||||
react_native_target("java/com/facebook/react/views/imagehelper:withmultisource"),
|
||||
react_native_target("java/com/facebook/react/views/modal:modal"),
|
||||
react_native_target("java/com/facebook/react/views/text:text"),
|
||||
react_native_target("java/com/facebook/react/views/textinput:textinput"),
|
||||
react_native_target("java/com/facebook/react/views/view:view"),
|
||||
react_native_target("java/com/facebook/react/views/viewpager:viewpager"),
|
||||
],
|
||||
)
|
|
@ -1,16 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/* package */ interface BitmapUpdateListener {
|
||||
public void onSecondaryAttach(Bitmap bitmap);
|
||||
public void onBitmapReady(Bitmap bitmap);
|
||||
public void onImageLoadEvent(int imageLoadEvent);
|
||||
}
|
|
@ -1,650 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroup;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
|
||||
/**
|
||||
* Abstract class for a {@link DrawCommandManager} with directional clipping. Allows support for
|
||||
* vertical and horizontal clipping by implementing abstract methods.
|
||||
*
|
||||
* Uses two dynamic programming arrays to efficiently update which views and commands are onscreen,
|
||||
* while not having to sort the incoming draw commands. The draw commands are loosely sorted, as
|
||||
* they represent a flattening of the normal view hierarchy, and we use that information to quickly
|
||||
* find children that should be considered onscreen. One array keeps track of, for each index, the
|
||||
* maximum bottom position that occurs at or before that index; the other keeps track of the
|
||||
* minimum top position that occurs at or after that index. Given the following children:
|
||||
*
|
||||
* +---------------------------------+ 0 (Y coordinate)
|
||||
* | 0 |
|
||||
* | +-----------+ | 10
|
||||
* | | 1 | |
|
||||
* | | | +--------------+ | 20
|
||||
* | | | | 3 | |
|
||||
* | +-----------+ | | | 30
|
||||
* | | | |
|
||||
* | +-----------+ | | | 40
|
||||
* | | 2 | | | |
|
||||
* | | | +--------------+ | 50
|
||||
* | | | |
|
||||
* | +-----------+ | 60
|
||||
* | |
|
||||
* +---------------------------------+ 70
|
||||
*
|
||||
* +-----------+ 80
|
||||
* | 4 |
|
||||
* | | +--------------+ 90
|
||||
* | | | 6 |
|
||||
* +-----------+ | | 100
|
||||
* | |
|
||||
* +-----------+ | | 110
|
||||
* | 5 | | |
|
||||
* | | +--------------+ 120
|
||||
* | |
|
||||
* +-----------+ 130
|
||||
*
|
||||
* The two arrays are:
|
||||
* 0 1 2 3 4 5 6
|
||||
* Max Bottom: [70, 70, 70, 70, 100, 130, 130]
|
||||
* Min Top: [ 0, 0, 0, 0, 80, 90, 90]
|
||||
*
|
||||
* We can then binary search for the first max bottom that is above our rect, and the first min top
|
||||
* that is below our rect.
|
||||
*
|
||||
* If the top and bottom of the rect are 55 and 85, respectively, we will start drawing at index 0
|
||||
* and stop at index 4.
|
||||
*
|
||||
* +---------------------------------+ 0 (Y coordinate)
|
||||
* | 0 |
|
||||
* | +-----------+ | 10
|
||||
* | | 1 | |
|
||||
* | | | +--------------+ | 20
|
||||
* | | | | 3 | |
|
||||
* | +-----------+ | | | 30
|
||||
* | | | |
|
||||
* | +-----------+ | | | 40
|
||||
* | | 2 | | | |
|
||||
* | | | +--------------+ | 50
|
||||
* - -| -| - - - -| - - - - - - |- - -
|
||||
* | +-----------+ | 60
|
||||
* | |
|
||||
* +---------------------------------+ 70
|
||||
*
|
||||
* +-----------+ 80
|
||||
* - - -| 4 - - - | - - - - - - - - -
|
||||
* | | +--------------+ 90
|
||||
* | | | 6 |
|
||||
* +-----------+ | | 100
|
||||
* | |
|
||||
* +-----------+ | | 110
|
||||
* | 5 | | |
|
||||
* | | +--------------+ 120
|
||||
* | |
|
||||
* +-----------+ 130
|
||||
*
|
||||
* If the top and bottom are 75 and 105 respectively, we will start drawing at index 4 and stop at
|
||||
* index 6.
|
||||
*
|
||||
* +---------------------------------+ 0 (Y coordinate)
|
||||
* | 0 |
|
||||
* | +-----------+ | 10
|
||||
* | | 1 | |
|
||||
* | | | +--------------+ | 20
|
||||
* | | | | 3 | |
|
||||
* | +-----------+ | | | 30
|
||||
* | | | |
|
||||
* | +-----------+ | | | 40
|
||||
* | | 2 | | | |
|
||||
* | | | +--------------+ | 50
|
||||
* | | | |
|
||||
* | +-----------+ | 60
|
||||
* | |
|
||||
* +---------------------------------+ 70
|
||||
* - - - - - - - - - - - - - - - -
|
||||
* +-----------+ 80
|
||||
* | 4 |
|
||||
* | | +--------------+ 90
|
||||
* | | | 6 |
|
||||
* +-----------+ | | 100
|
||||
* - - - - - - - |- - - - - | - - -
|
||||
* +-----------+ | | 110
|
||||
* | 5 | | |
|
||||
* | | +--------------+ 120
|
||||
* | |
|
||||
* +-----------+ 130
|
||||
*
|
||||
* While this doesn't map exactly to all of the commands that could be clipped, it means that
|
||||
* children which contain other children (a pretty common case when flattening views) are clipped
|
||||
* or unclipped as one logical unit. This has the side effect of minimizing the amount of
|
||||
* invalidates coming from minor clipping rect adjustments. The underlying dynamic programming
|
||||
* arrays can be calculated off the UI thread in O(n) time, requiring just two passes through the
|
||||
* command array.
|
||||
*
|
||||
* We do a similar optimization when searching for matching node regions, as node regions are
|
||||
* loosely sorted as well when clipping.
|
||||
*/
|
||||
/* package */ abstract class ClippingDrawCommandManager extends DrawCommandManager {
|
||||
|
||||
private static final String TAG = ClippingDrawCommandManager.class.getSimpleName();
|
||||
|
||||
private final FlatViewGroup mFlatViewGroup;
|
||||
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
|
||||
protected float[] mCommandMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
|
||||
protected float[] mCommandMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
|
||||
|
||||
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
|
||||
protected float[] mRegionMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
|
||||
protected float[] mRegionMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
|
||||
|
||||
// Onscreen bounds of draw command array.
|
||||
private int mStart;
|
||||
private int mStop;
|
||||
|
||||
// Mapping of ids to index position within the draw command array. O(log n) lookups should be
|
||||
// less in our case because of the large constant overhead and auto boxing of the map.
|
||||
private SparseIntArray mDrawViewIndexMap = StateBuilder.EMPTY_SPARSE_INT;
|
||||
// Map of views that are currently clipped.
|
||||
private final SparseArray<View> mClippedSubviews = new SparseArray<>();
|
||||
|
||||
protected final Rect mClippingRect = new Rect();
|
||||
|
||||
// Used in updating the clipping rect, as sometimes we want to detach all views, which means we
|
||||
// need to temporarily store the views we are detaching and removing. These are always of size
|
||||
// 0, except when used in update clipping rect.
|
||||
private final SparseArray<View> mViewsToRemove = new SparseArray<>();
|
||||
private final ArrayList<View> mViewsToKeep = new ArrayList<>();
|
||||
|
||||
// Currently clipping ViewGroups
|
||||
private final ArrayList<ReactClippingViewGroup> mClippingViewGroups = new ArrayList<>();
|
||||
|
||||
ClippingDrawCommandManager(FlatViewGroup flatViewGroup, DrawCommand[] drawCommands) {
|
||||
mFlatViewGroup = flatViewGroup;
|
||||
initialSetup(drawCommands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initially setup this instance. Makes sure the draw commands are mounted, and that our
|
||||
* clipping rect reflects our current bounds.
|
||||
*
|
||||
* @param drawCommands The list of current draw commands. In current implementations, this will
|
||||
* always be DrawCommand.EMPTY_ARRAY
|
||||
*/
|
||||
private void initialSetup(DrawCommand[] drawCommands) {
|
||||
mountDrawCommands(
|
||||
drawCommands,
|
||||
mDrawViewIndexMap,
|
||||
mCommandMaxBottom,
|
||||
mCommandMinTop,
|
||||
true);
|
||||
updateClippingRect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of the first command that could be on the screen.
|
||||
*/
|
||||
abstract int commandStartIndex();
|
||||
|
||||
/**
|
||||
* @return index of the first command that is guaranteed to be off the screen, starting from the
|
||||
* given start.
|
||||
*/
|
||||
abstract int commandStopIndex(int start);
|
||||
|
||||
/**
|
||||
* @return index of the first region that is guaranteed to be outside of the bounds for touch.
|
||||
*/
|
||||
abstract int regionStopIndex(float touchX, float touchY);
|
||||
|
||||
/**
|
||||
* Whether an index and all indices before it are guaranteed to be out of bounds for the current
|
||||
* touch.
|
||||
*
|
||||
* @param index The region index to check.
|
||||
* @param touchX X coordinate.
|
||||
* @param touchY Y coordinate.
|
||||
* @return true if the index and all before it are out of bounds.
|
||||
*/
|
||||
abstract boolean regionAboveTouch(int index, float touchX, float touchY);
|
||||
|
||||
@Override
|
||||
public void mountDrawCommands(
|
||||
DrawCommand[] drawCommands,
|
||||
SparseIntArray drawViewIndexMap,
|
||||
float[] maxBottom,
|
||||
float[] minTop,
|
||||
boolean willMountViews) {
|
||||
mDrawCommands = drawCommands;
|
||||
mCommandMaxBottom = maxBottom;
|
||||
mCommandMinTop = minTop;
|
||||
mDrawViewIndexMap = drawViewIndexMap;
|
||||
if (mClippingRect.bottom != mClippingRect.top) {
|
||||
mStart = commandStartIndex();
|
||||
mStop = commandStopIndex(mStart);
|
||||
if (!willMountViews) {
|
||||
// If we are not mounting views, we still need to update view indices and positions. It is
|
||||
// possible that a child changed size and we still need new clipping even though we are not
|
||||
// mounting views.
|
||||
updateClippingToCurrentRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mountNodeRegions(NodeRegion[] nodeRegions, float[] maxBottom, float[] minTop) {
|
||||
mNodeRegions = nodeRegions;
|
||||
mRegionMaxBottom = maxBottom;
|
||||
mRegionMinTop = minTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY) {
|
||||
int i = regionStopIndex(touchX, touchY);
|
||||
while (i-- > 0) {
|
||||
NodeRegion nodeRegion = mNodeRegions[i];
|
||||
if (!nodeRegion.mIsVirtual) {
|
||||
// only interested in virtual nodes
|
||||
continue;
|
||||
}
|
||||
if (regionAboveTouch(i, touchX, touchY)) {
|
||||
break;
|
||||
}
|
||||
if (nodeRegion.withinBounds(touchX, touchY)) {
|
||||
return nodeRegion;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY) {
|
||||
int i = regionStopIndex(touchX, touchY);
|
||||
while (i-- > 0) {
|
||||
NodeRegion nodeRegion = mNodeRegions[i];
|
||||
if (regionAboveTouch(i, touchX, touchY)) {
|
||||
break;
|
||||
}
|
||||
if (nodeRegion.withinBounds(touchX, touchY)) {
|
||||
return nodeRegion;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void clip(int id, View view) {
|
||||
mClippedSubviews.put(id, view);
|
||||
}
|
||||
|
||||
private void unclip(int id) {
|
||||
mClippedSubviews.remove(id);
|
||||
}
|
||||
|
||||
private boolean isClipped(int id) {
|
||||
return mClippedSubviews.get(id) != null;
|
||||
}
|
||||
|
||||
private boolean isNotClipped(int id) {
|
||||
return mClippedSubviews.get(id) == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onClippedViewDropped(View view) {
|
||||
unclip(view.getId());
|
||||
mFlatViewGroup.removeDetachedView(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach) {
|
||||
mClippingViewGroups.clear();
|
||||
for (int viewToAdd : viewsToAdd) {
|
||||
// Views that are just temporarily detached are marked with a negative value.
|
||||
boolean newView = viewToAdd > 0;
|
||||
if (!newView) {
|
||||
viewToAdd = -viewToAdd;
|
||||
}
|
||||
|
||||
int commandArrayIndex = mDrawViewIndexMap.get(viewToAdd);
|
||||
DrawView drawView = (DrawView) mDrawCommands[commandArrayIndex];
|
||||
View view = viewResolver.getView(drawView.reactTag);
|
||||
ensureViewHasNoParent(view);
|
||||
|
||||
// these views need to support recursive clipping of subviews
|
||||
if (view instanceof ReactClippingViewGroup &&
|
||||
((ReactClippingViewGroup) view).getRemoveClippedSubviews()) {
|
||||
mClippingViewGroups.add((ReactClippingViewGroup) view);
|
||||
}
|
||||
|
||||
if (newView) {
|
||||
// This view was not previously attached to this parent.
|
||||
drawView.mWasMounted = true;
|
||||
if (animating(view) || withinBounds(commandArrayIndex)) {
|
||||
// View should be drawn. This view can't currently be clipped because it wasn't
|
||||
// previously attached to this parent.
|
||||
mFlatViewGroup.addViewInLayout(view);
|
||||
} else {
|
||||
clip(drawView.reactTag, view);
|
||||
}
|
||||
} else {
|
||||
// This view was previously attached, and just temporarily detached.
|
||||
if (drawView.mWasMounted) {
|
||||
// The DrawView has been mounted before.
|
||||
if (isNotClipped(drawView.reactTag)) {
|
||||
// The DrawView is not clipped. Attach it.
|
||||
mFlatViewGroup.attachViewToParent(view);
|
||||
}
|
||||
// else The DrawView has been previously mounted and is clipped, so don't attach it.
|
||||
} else {
|
||||
// We are mounting it, so lets get this part out of the way.
|
||||
drawView.mWasMounted = true;
|
||||
// The DrawView has not been mounted before, which means the bounds changed and triggered
|
||||
// a new DrawView when it was collected from the shadow node. We have a view with the
|
||||
// same id temporarily detached, but its bounds have changed.
|
||||
if (animating(view) || withinBounds(commandArrayIndex)) {
|
||||
// View should be drawn.
|
||||
if (isClipped(drawView.reactTag)) {
|
||||
// View was clipped, so add it.
|
||||
mFlatViewGroup.addViewInLayout(view);
|
||||
unclip(drawView.reactTag);
|
||||
} else {
|
||||
// View was just temporarily removed, so attach it. We already know it isn't clipped,
|
||||
// so no need to unclip it.
|
||||
mFlatViewGroup.attachViewToParent(view);
|
||||
}
|
||||
} else {
|
||||
// View should be clipped.
|
||||
if (isNotClipped(drawView.reactTag)) {
|
||||
// View was onscreen.
|
||||
mFlatViewGroup.removeDetachedView(view);
|
||||
clip(drawView.reactTag, view);
|
||||
}
|
||||
// else view is already clipped and not within bounds.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int viewToDetach : viewsToDetach) {
|
||||
View view = viewResolver.getView(viewToDetach);
|
||||
if (view.getParent() != null) {
|
||||
throw new RuntimeException("Trying to remove view not owned by FlatViewGroup");
|
||||
} else {
|
||||
mFlatViewGroup.removeDetachedView(view);
|
||||
}
|
||||
// The view isn't clipped anymore, but gone entirely.
|
||||
unclip(viewToDetach);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a view is currently animating.
|
||||
*/
|
||||
private static boolean animating(View view) {
|
||||
Animation animation = view.getAnimation();
|
||||
return animation != null && !animation.hasEnded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a command index is currently onscreen.
|
||||
*/
|
||||
private boolean withinBounds(int i) {
|
||||
return mStart <= i && i < mStop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateClippingRect() {
|
||||
ReactClippingViewGroupHelper.calculateClippingRect(mFlatViewGroup, mClippingRect);
|
||||
if (mFlatViewGroup.getParent() == null || mClippingRect.top == mClippingRect.bottom) {
|
||||
// If we are unparented or are clipping to an empty rect, no op. Return false so we don't
|
||||
// invalidate.
|
||||
return false;
|
||||
}
|
||||
|
||||
int start = commandStartIndex();
|
||||
int stop = commandStopIndex(start);
|
||||
if (mStart <= start && stop <= mStop) {
|
||||
// We would only be removing children, don't invalidate and don't bother changing the
|
||||
// attached children.
|
||||
updateClippingRecursively();
|
||||
return false;
|
||||
}
|
||||
|
||||
mStart = start;
|
||||
mStop = stop;
|
||||
|
||||
updateClippingToCurrentRect();
|
||||
updateClippingRecursively();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateClippingRecursively() {
|
||||
for (int i = 0, children = mClippingViewGroups.size(); i < children; i++) {
|
||||
ReactClippingViewGroup view = mClippingViewGroups.get(i);
|
||||
if (isNotClipped(((View) view).getId())) {
|
||||
view.updateClippingRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used either after we have updated the current rect, or when we have mounted new commands and
|
||||
* the rect hasn't changed. Updates the clipping after mStart and mStop have been set to the
|
||||
* correct values. For draw commands, this is all it takes to update the command mounting, as
|
||||
* draw commands are only attached in a conceptual sense, and don't rely on the android view
|
||||
* hierarchy.
|
||||
*
|
||||
* For native children, we have to walk through our current views and remove any that are no
|
||||
* longer on screen, and add those that are newly on screen. As an optimization for fling, if we
|
||||
* are removing two or more native views we instead detachAllViews from the {@link FlatViewGroup}
|
||||
* and re-attach or add as needed.
|
||||
*
|
||||
* This approximation is roughly correct, as we tend to add and remove the same amount of views,
|
||||
* and each add and remove pair is O(n); detachAllViews and re-attach requires two passes, so
|
||||
* using this once we are removing more than two native views is a good breakpoint.
|
||||
*/
|
||||
private void updateClippingToCurrentRect() {
|
||||
for (int i = 0, size = mFlatViewGroup.getChildCount(); i < size; i++) {
|
||||
View view = mFlatViewGroup.getChildAt(i);
|
||||
int index = mDrawViewIndexMap.get(view.getId());
|
||||
if (withinBounds(index) || animating(view)) {
|
||||
mViewsToKeep.add(view);
|
||||
} else {
|
||||
mViewsToRemove.append(i, view);
|
||||
clip(view.getId(), view);
|
||||
}
|
||||
}
|
||||
|
||||
int removeSize = mViewsToRemove.size();
|
||||
boolean removeAll = removeSize > 2;
|
||||
|
||||
if (removeAll) {
|
||||
// Detach all, as we are changing quite a few views, whether flinging or otherwise.
|
||||
mFlatViewGroup.detachAllViewsFromParent();
|
||||
|
||||
for (int i = 0; i < removeSize; i++) {
|
||||
mFlatViewGroup.removeDetachedView(mViewsToRemove.valueAt(i));
|
||||
}
|
||||
} else {
|
||||
// Simple clipping sweep, as we are changing relatively few views.
|
||||
while (removeSize-- > 0) {
|
||||
mFlatViewGroup.removeViewsInLayout(mViewsToRemove.keyAt(removeSize), 1);
|
||||
}
|
||||
}
|
||||
mViewsToRemove.clear();
|
||||
|
||||
int current = mStart;
|
||||
int childIndex = 0;
|
||||
|
||||
for (int i = 0, size = mViewsToKeep.size(); i < size; i++) {
|
||||
View view = mViewsToKeep.get(i);
|
||||
int commandIndex = mDrawViewIndexMap.get(view.getId());
|
||||
if (current <= commandIndex) {
|
||||
while (current != commandIndex) {
|
||||
if (mDrawCommands[current] instanceof DrawView) {
|
||||
DrawView drawView = (DrawView) mDrawCommands[current];
|
||||
mFlatViewGroup.addViewInLayout(
|
||||
Assertions.assumeNotNull(mClippedSubviews.get(drawView.reactTag)),
|
||||
childIndex++);
|
||||
unclip(drawView.reactTag);
|
||||
}
|
||||
current++;
|
||||
}
|
||||
// We are currently at the command index, but we want to increment beyond it.
|
||||
current++;
|
||||
}
|
||||
if (removeAll) {
|
||||
mFlatViewGroup.attachViewToParent(view, childIndex);
|
||||
}
|
||||
// We want to make sure we increment the child index even if we didn't detach it to maintain
|
||||
// order.
|
||||
childIndex++;
|
||||
}
|
||||
mViewsToKeep.clear();
|
||||
|
||||
while (current < mStop) {
|
||||
if (mDrawCommands[current] instanceof DrawView) {
|
||||
DrawView drawView = (DrawView) mDrawCommands[current];
|
||||
mFlatViewGroup.addViewInLayout(
|
||||
Assertions.assumeNotNull(mClippedSubviews.get(drawView.reactTag)),
|
||||
childIndex++);
|
||||
unclip(drawView.reactTag);
|
||||
}
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getClippingRect(Rect outClippingRect) {
|
||||
outClippingRect.set(mClippingRect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SparseArray<View> getDetachedViews() {
|
||||
return mClippedSubviews;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the unclipped commands on the given canvas. This would be much simpler if we didn't
|
||||
* have to worry about animating views, as we could simply:
|
||||
*
|
||||
* for (int i = start; i < stop; i++) {
|
||||
* drawCommands[i].draw(...);
|
||||
* }
|
||||
*
|
||||
* This is complicated however by animating views, which may occur before or after the current
|
||||
* clipping rect. Consider the following array:
|
||||
*
|
||||
* +--------------+
|
||||
* | DrawView | 0
|
||||
* | *animating* |
|
||||
* +--------------+
|
||||
* | DrawCommmand | 1
|
||||
* | *clipped* |
|
||||
* +--------------+
|
||||
* | DrawCommand | 2 start
|
||||
* | |
|
||||
* +--------------+
|
||||
* | DrawCommand | 3
|
||||
* | |
|
||||
* +--------------+
|
||||
* | DrawView | 4
|
||||
* | |
|
||||
* +--------------+
|
||||
* | DrawView | 5 stop
|
||||
* | *clipped* |
|
||||
* +--------------+
|
||||
* | DrawView | 6
|
||||
* | *animating* |
|
||||
* +--------------+
|
||||
*
|
||||
* 2, 3, and 4 are onscreen according to bounds, while 0 and 6 are onscreen according to
|
||||
* animation. We have to walk through the attached children making sure to draw any draw
|
||||
* commands that should be drawn before that draw view, as well as making sure not to draw any
|
||||
* draw commands that are out of bounds.
|
||||
*
|
||||
* @param canvas The canvas to draw on.
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int commandIndex = mStart;
|
||||
int size = mFlatViewGroup.getChildCount();
|
||||
|
||||
// Iterate through the children, making sure that we draw any draw commands we haven't drawn
|
||||
// that should happen before the next draw view.
|
||||
for (int i = 0; i < size; i++) {
|
||||
// This is the command index of the next view that we need to draw. Since a view might be
|
||||
// animating, this view is either before all the commands onscreen, onscreen, or after the
|
||||
// onscreen commands.
|
||||
int viewIndex = mDrawViewIndexMap.get(mFlatViewGroup.getChildAt(i).getId());
|
||||
if (mStop < viewIndex) {
|
||||
// The current view is outside of the viewport bounds. We want to draw all the commands
|
||||
// up to the stop, then draw all the views outside the viewport bounds.
|
||||
while (commandIndex < mStop) {
|
||||
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
|
||||
}
|
||||
// We are now out of commands to draw, so we could just draw the remaining attached
|
||||
// children, but the for loop logic will draw the rest anyway.
|
||||
} else if (commandIndex <= viewIndex) {
|
||||
// The viewIndex is within our onscreen bounds (or == stop). We want to draw all the
|
||||
// commands from the current position to the current view, inclusive.
|
||||
while (commandIndex < viewIndex) {
|
||||
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
|
||||
}
|
||||
// Command index now == viewIndex, so increment beyond it.
|
||||
commandIndex++;
|
||||
}
|
||||
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas);
|
||||
}
|
||||
|
||||
// If we get here, it means we have drawn all the views, now just draw the remaining draw
|
||||
// commands.
|
||||
while (commandIndex < mStop) {
|
||||
DrawCommand command = mDrawCommands[commandIndex++];
|
||||
if (command instanceof DrawView) {
|
||||
// We should never have more DrawView commands at this point. But in case we do, fail safely
|
||||
// by ignoring the DrawView command
|
||||
FLog.w(
|
||||
TAG,
|
||||
"Unexpected DrawView command at index " + (commandIndex-1) + " with mStop=" +
|
||||
mStop + ". " + Arrays.toString(mDrawCommands));
|
||||
continue;
|
||||
}
|
||||
command.draw(mFlatViewGroup, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void debugDraw(Canvas canvas) {
|
||||
// Draws clipped draw commands, but does not draw clipped views.
|
||||
for (DrawCommand drawCommand : mDrawCommands) {
|
||||
if (drawCommand instanceof DrawView) {
|
||||
if (isNotClipped(((DrawView) drawCommand).reactTag)) {
|
||||
drawCommand.debugDraw(mFlatViewGroup, canvas);
|
||||
}
|
||||
// else, don't draw, and don't increment index
|
||||
} else {
|
||||
drawCommand.debugDraw(mFlatViewGroup, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
|
||||
/**
|
||||
* Draws background for a FlatShadowNode as a solid rectangle.
|
||||
*/
|
||||
/* package */ final class DrawBackgroundColor extends AbstractDrawCommand {
|
||||
|
||||
private static final Paint PAINT = new Paint();
|
||||
|
||||
private final int mBackgroundColor;
|
||||
|
||||
/* package */ DrawBackgroundColor(int backgroundColor) {
|
||||
mBackgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
PAINT.setColor(mBackgroundColor);
|
||||
canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), PAINT);
|
||||
}
|
||||
}
|
|
@ -1,458 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
|
||||
/* package */ final class DrawBorder extends AbstractDrawBorder {
|
||||
|
||||
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private static final float[] TMP_FLOAT_ARRAY = new float[4];
|
||||
|
||||
private static final int BORDER_STYLE_SOLID = 0;
|
||||
private static final int BORDER_STYLE_DOTTED = 1;
|
||||
private static final int BORDER_STYLE_DASHED = 2;
|
||||
|
||||
private static final int BORDER_LEFT_COLOR_SET = 1 << 1;
|
||||
private static final int BORDER_TOP_COLOR_SET = 1 << 2;
|
||||
private static final int BORDER_RIGHT_COLOR_SET = 1 << 3;
|
||||
private static final int BORDER_BOTTOM_COLOR_SET = 1 << 4;
|
||||
private static final int BORDER_PATH_EFFECT_DIRTY = 1 << 5;
|
||||
|
||||
// ~0 == 0xFFFFFFFF, all bits set to 1.
|
||||
private static final int ALL_BITS_SET = ~0;
|
||||
// 0 == 0x00000000, all bits set to 0.
|
||||
private static final int ALL_BITS_UNSET = 0;
|
||||
|
||||
private float mBorderLeftWidth;
|
||||
private float mBorderTopWidth;
|
||||
private float mBorderRightWidth;
|
||||
private float mBorderBottomWidth;
|
||||
|
||||
private int mBorderLeftColor;
|
||||
private int mBorderTopColor;
|
||||
private int mBorderRightColor;
|
||||
private int mBorderBottomColor;
|
||||
|
||||
private int mBorderStyle = BORDER_STYLE_SOLID;
|
||||
|
||||
private int mBackgroundColor;
|
||||
|
||||
private @Nullable DashPathEffect mPathEffectForBorderStyle;
|
||||
private @Nullable Path mPathForBorder;
|
||||
|
||||
public void setBorderWidth(int position, float borderWidth) {
|
||||
switch (position) {
|
||||
case Spacing.LEFT:
|
||||
mBorderLeftWidth = borderWidth;
|
||||
break;
|
||||
case Spacing.TOP:
|
||||
mBorderTopWidth = borderWidth;
|
||||
break;
|
||||
case Spacing.RIGHT:
|
||||
mBorderRightWidth = borderWidth;
|
||||
break;
|
||||
case Spacing.BOTTOM:
|
||||
mBorderBottomWidth = borderWidth;
|
||||
break;
|
||||
case Spacing.ALL:
|
||||
setBorderWidth(borderWidth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public float getBorderWidth(int position) {
|
||||
switch (position) {
|
||||
case Spacing.LEFT:
|
||||
return mBorderLeftWidth;
|
||||
case Spacing.TOP:
|
||||
return mBorderTopWidth;
|
||||
case Spacing.RIGHT:
|
||||
return mBorderRightWidth;
|
||||
case Spacing.BOTTOM:
|
||||
return mBorderBottomWidth;
|
||||
case Spacing.ALL:
|
||||
return getBorderWidth();
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public void setBorderStyle(@Nullable String style) {
|
||||
if ("dotted".equals(style)) {
|
||||
mBorderStyle = BORDER_STYLE_DOTTED;
|
||||
} else if ("dashed".equals(style)) {
|
||||
mBorderStyle = BORDER_STYLE_DASHED;
|
||||
} else {
|
||||
mBorderStyle = BORDER_STYLE_SOLID;
|
||||
}
|
||||
|
||||
setFlag(BORDER_PATH_EFFECT_DIRTY);
|
||||
}
|
||||
|
||||
public void resetBorderColor(int position) {
|
||||
switch (position) {
|
||||
case Spacing.LEFT:
|
||||
resetFlag(BORDER_LEFT_COLOR_SET);
|
||||
break;
|
||||
case Spacing.TOP:
|
||||
resetFlag(BORDER_TOP_COLOR_SET);
|
||||
break;
|
||||
case Spacing.RIGHT:
|
||||
resetFlag(BORDER_RIGHT_COLOR_SET);
|
||||
break;
|
||||
case Spacing.BOTTOM:
|
||||
resetFlag(BORDER_BOTTOM_COLOR_SET);
|
||||
break;
|
||||
case Spacing.ALL:
|
||||
setBorderColor(Color.BLACK);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBorderColor(int position, int borderColor) {
|
||||
switch (position) {
|
||||
case Spacing.LEFT:
|
||||
mBorderLeftColor = borderColor;
|
||||
setFlag(BORDER_LEFT_COLOR_SET);
|
||||
break;
|
||||
case Spacing.TOP:
|
||||
mBorderTopColor = borderColor;
|
||||
setFlag(BORDER_TOP_COLOR_SET);
|
||||
break;
|
||||
case Spacing.RIGHT:
|
||||
mBorderRightColor = borderColor;
|
||||
setFlag(BORDER_RIGHT_COLOR_SET);
|
||||
break;
|
||||
case Spacing.BOTTOM:
|
||||
mBorderBottomColor = borderColor;
|
||||
setFlag(BORDER_BOTTOM_COLOR_SET);
|
||||
break;
|
||||
case Spacing.ALL:
|
||||
setBorderColor(borderColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public int getBorderColor(int position) {
|
||||
int defaultColor = getBorderColor();
|
||||
switch (position) {
|
||||
case Spacing.LEFT:
|
||||
return resolveBorderColor(BORDER_LEFT_COLOR_SET, mBorderLeftColor, defaultColor);
|
||||
case Spacing.TOP:
|
||||
return resolveBorderColor(BORDER_TOP_COLOR_SET, mBorderTopColor, defaultColor);
|
||||
case Spacing.RIGHT:
|
||||
return resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor);
|
||||
case Spacing.BOTTOM:
|
||||
return resolveBorderColor(BORDER_BOTTOM_COLOR_SET, mBorderBottomColor, defaultColor);
|
||||
case Spacing.ALL:
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
mBackgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (getBorderRadius() >= 0.5f || getPathEffectForBorderStyle() != null) {
|
||||
drawRoundedBorders(canvas);
|
||||
} else {
|
||||
drawRectangularBorders(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable DashPathEffect getPathEffectForBorderStyle() {
|
||||
if (isFlagSet(BORDER_PATH_EFFECT_DIRTY)) {
|
||||
switch (mBorderStyle) {
|
||||
case BORDER_STYLE_DOTTED:
|
||||
mPathEffectForBorderStyle = createDashPathEffect(getBorderWidth());
|
||||
break;
|
||||
|
||||
case BORDER_STYLE_DASHED:
|
||||
mPathEffectForBorderStyle = createDashPathEffect(getBorderWidth() * 3);
|
||||
break;
|
||||
|
||||
default:
|
||||
mPathEffectForBorderStyle = null;
|
||||
break;
|
||||
}
|
||||
|
||||
resetFlag(BORDER_PATH_EFFECT_DIRTY);
|
||||
}
|
||||
|
||||
return mPathEffectForBorderStyle;
|
||||
}
|
||||
|
||||
private void drawRoundedBorders(Canvas canvas) {
|
||||
if (mBackgroundColor != 0) {
|
||||
PAINT.setColor(mBackgroundColor);
|
||||
canvas.drawPath(getPathForBorderRadius(), PAINT);
|
||||
}
|
||||
|
||||
drawBorders(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly determine if all the set border colors are equal. Bitwise AND all the set colors
|
||||
* together, then OR them all together. If the AND and the OR are the same, then the colors
|
||||
* are compatible, so return this color.
|
||||
*
|
||||
* Used to avoid expensive path creation and expensive calls to canvas.drawPath
|
||||
*
|
||||
* @return A compatible border color, or zero if the border colors are not compatible.
|
||||
*/
|
||||
private static int fastBorderCompatibleColorOrZero(
|
||||
float borderLeft,
|
||||
float borderTop,
|
||||
float borderRight,
|
||||
float borderBottom,
|
||||
int colorLeft,
|
||||
int colorTop,
|
||||
int colorRight,
|
||||
int colorBottom) {
|
||||
int andSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_SET) &
|
||||
(borderTop > 0 ? colorTop : ALL_BITS_SET) &
|
||||
(borderRight > 0 ? colorRight : ALL_BITS_SET) &
|
||||
(borderBottom > 0 ? colorBottom : ALL_BITS_SET);
|
||||
int orSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_UNSET) |
|
||||
(borderTop > 0 ? colorTop : ALL_BITS_UNSET) |
|
||||
(borderRight > 0 ? colorRight : ALL_BITS_UNSET) |
|
||||
(borderBottom > 0 ? colorBottom : ALL_BITS_UNSET);
|
||||
return andSmear == orSmear ? andSmear : 0;
|
||||
}
|
||||
|
||||
private void drawRectangularBorders(Canvas canvas) {
|
||||
int defaultColor = getBorderColor();
|
||||
float defaultWidth = getBorderWidth();
|
||||
|
||||
float top = getTop();
|
||||
float borderTop = resolveWidth(mBorderTopWidth, defaultWidth);
|
||||
float topInset = top + borderTop;
|
||||
int topColor = resolveBorderColor(BORDER_TOP_COLOR_SET, mBorderTopColor, defaultColor);
|
||||
|
||||
float bottom = getBottom();
|
||||
float borderBottom = resolveWidth(mBorderBottomWidth, defaultWidth);
|
||||
float bottomInset = bottom - borderBottom;
|
||||
int bottomColor = resolveBorderColor(BORDER_BOTTOM_COLOR_SET, mBorderBottomColor, defaultColor);
|
||||
|
||||
float left = getLeft();
|
||||
float borderLeft = resolveWidth(mBorderLeftWidth, defaultWidth);
|
||||
float leftInset = left + borderLeft;
|
||||
int leftColor = resolveBorderColor(BORDER_LEFT_COLOR_SET, mBorderLeftColor, defaultColor);
|
||||
|
||||
float right = getRight();
|
||||
float borderRight = resolveWidth(mBorderRightWidth, defaultWidth);
|
||||
float rightInset = right - borderRight;
|
||||
int rightColor = resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor);
|
||||
|
||||
// Check for fast path to border drawing.
|
||||
int fastBorderColor = fastBorderCompatibleColorOrZero(
|
||||
borderLeft,
|
||||
borderTop,
|
||||
borderRight,
|
||||
borderBottom,
|
||||
leftColor,
|
||||
topColor,
|
||||
rightColor,
|
||||
bottomColor);
|
||||
if (fastBorderColor != 0) {
|
||||
// Fast border color draw.
|
||||
if (Color.alpha(fastBorderColor) != 0) {
|
||||
// Border color is not transparent.
|
||||
|
||||
// Draw center.
|
||||
if (Color.alpha(mBackgroundColor) != 0) {
|
||||
PAINT.setColor(mBackgroundColor);
|
||||
if (Color.alpha(fastBorderColor) == 255) {
|
||||
// The border will draw over the edges, so only draw the inset background.
|
||||
canvas.drawRect(leftInset, topInset, rightInset, bottomInset, PAINT);
|
||||
} else {
|
||||
// The border is opaque, so we have to draw the entire background color.
|
||||
canvas.drawRect(left, top, right, bottom, PAINT);
|
||||
}
|
||||
}
|
||||
|
||||
PAINT.setColor(fastBorderColor);
|
||||
if (borderLeft > 0) {
|
||||
canvas.drawRect(left, top, leftInset, bottom - borderBottom, PAINT);
|
||||
}
|
||||
if (borderTop > 0) {
|
||||
canvas.drawRect(left + borderLeft, top, right, topInset, PAINT);
|
||||
}
|
||||
if (borderRight > 0) {
|
||||
canvas.drawRect(rightInset, top + borderTop, right, bottom, PAINT);
|
||||
}
|
||||
if (borderBottom > 0) {
|
||||
canvas.drawRect(left, bottomInset, right - borderRight, bottom, PAINT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mPathForBorder == null) {
|
||||
mPathForBorder = new Path();
|
||||
}
|
||||
|
||||
// Draw center. Any of the borders might be opaque or transparent, so we need to draw this.
|
||||
if (Color.alpha(mBackgroundColor) != 0) {
|
||||
PAINT.setColor(mBackgroundColor);
|
||||
canvas.drawRect(left, top, right, bottom, PAINT);
|
||||
}
|
||||
|
||||
// Draw top.
|
||||
if (borderTop != 0 && Color.alpha(topColor) != 0) {
|
||||
PAINT.setColor(topColor);
|
||||
updatePathForTopBorder(
|
||||
mPathForBorder,
|
||||
top,
|
||||
topInset,
|
||||
left,
|
||||
leftInset,
|
||||
right,
|
||||
rightInset);
|
||||
canvas.drawPath(mPathForBorder, PAINT);
|
||||
}
|
||||
|
||||
// Draw bottom.
|
||||
if (borderBottom != 0 && Color.alpha(bottomColor) != 0) {
|
||||
PAINT.setColor(bottomColor);
|
||||
updatePathForBottomBorder(
|
||||
mPathForBorder,
|
||||
bottom,
|
||||
bottomInset,
|
||||
left,
|
||||
leftInset,
|
||||
right,
|
||||
rightInset);
|
||||
canvas.drawPath(mPathForBorder, PAINT);
|
||||
}
|
||||
|
||||
// Draw left.
|
||||
if (borderLeft != 0 && Color.alpha(leftColor) != 0) {
|
||||
PAINT.setColor(leftColor);
|
||||
updatePathForLeftBorder(
|
||||
mPathForBorder,
|
||||
top,
|
||||
topInset,
|
||||
bottom,
|
||||
bottomInset,
|
||||
left,
|
||||
leftInset);
|
||||
canvas.drawPath(mPathForBorder, PAINT);
|
||||
}
|
||||
|
||||
// Draw right.
|
||||
if (borderRight != 0 && Color.alpha(rightColor) != 0) {
|
||||
PAINT.setColor(rightColor);
|
||||
updatePathForRightBorder(
|
||||
mPathForBorder,
|
||||
top,
|
||||
topInset,
|
||||
bottom,
|
||||
bottomInset,
|
||||
right,
|
||||
rightInset);
|
||||
canvas.drawPath(mPathForBorder, PAINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void updatePathForTopBorder(
|
||||
Path path,
|
||||
float top,
|
||||
float topInset,
|
||||
float left,
|
||||
float leftInset,
|
||||
float right,
|
||||
float rightInset) {
|
||||
path.reset();
|
||||
path.moveTo(left, top);
|
||||
path.lineTo(leftInset, topInset);
|
||||
path.lineTo(rightInset, topInset);
|
||||
path.lineTo(right, top);
|
||||
path.lineTo(left, top);
|
||||
}
|
||||
|
||||
private static void updatePathForBottomBorder(
|
||||
Path path,
|
||||
float bottom,
|
||||
float bottomInset,
|
||||
float left,
|
||||
float leftInset,
|
||||
float right,
|
||||
float rightInset) {
|
||||
path.reset();
|
||||
path.moveTo(left, bottom);
|
||||
path.lineTo(right, bottom);
|
||||
path.lineTo(rightInset, bottomInset);
|
||||
path.lineTo(leftInset, bottomInset);
|
||||
path.lineTo(left, bottom);
|
||||
}
|
||||
|
||||
private static void updatePathForLeftBorder(
|
||||
Path path,
|
||||
float top,
|
||||
float topInset,
|
||||
float bottom,
|
||||
float bottomInset,
|
||||
float left,
|
||||
float leftInset) {
|
||||
path.reset();
|
||||
path.moveTo(left, top);
|
||||
path.lineTo(leftInset, topInset);
|
||||
path.lineTo(leftInset, bottomInset);
|
||||
path.lineTo(left, bottom);
|
||||
path.lineTo(left, top);
|
||||
}
|
||||
|
||||
private static void updatePathForRightBorder(
|
||||
Path path,
|
||||
float top,
|
||||
float topInset,
|
||||
float bottom,
|
||||
float bottomInset,
|
||||
float right,
|
||||
float rightInset) {
|
||||
path.reset();
|
||||
path.moveTo(right, top);
|
||||
path.lineTo(right, bottom);
|
||||
path.lineTo(rightInset, bottomInset);
|
||||
path.lineTo(rightInset, topInset);
|
||||
path.lineTo(right, top);
|
||||
}
|
||||
|
||||
private int resolveBorderColor(int flag, int color, int defaultColor) {
|
||||
return isFlagSet(flag) ? color : defaultColor;
|
||||
}
|
||||
|
||||
private static float resolveWidth(float width, float defaultWidth) {
|
||||
return (width == 0 || /* check for NaN */ width != width) ? defaultWidth : width;
|
||||
}
|
||||
|
||||
private static DashPathEffect createDashPathEffect(float borderWidth) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
TMP_FLOAT_ARRAY[i] = borderWidth;
|
||||
}
|
||||
return new DashPathEffect(TMP_FLOAT_ARRAY, 0);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
|
||||
/**
|
||||
* DrawCommand is an interface that shadow nodes need to implement to do the drawing.
|
||||
* Instances of DrawCommand are created in background thread and passed to UI thread.
|
||||
* Once a DrawCommand is shared with UI thread, it can no longer be mutated in background thread.
|
||||
*/
|
||||
public abstract class DrawCommand {
|
||||
// used by StateBuilder, FlatViewGroup and FlatShadowNode
|
||||
/* package */ static final DrawCommand[] EMPTY_ARRAY = new DrawCommand[0];
|
||||
|
||||
/**
|
||||
* Performs drawing into the given canvas.
|
||||
*
|
||||
* @param parent The parent to get child information from, if needed
|
||||
* @param canvas The canvas to draw into
|
||||
*/
|
||||
abstract void draw(FlatViewGroup parent, Canvas canvas);
|
||||
|
||||
/**
|
||||
* Performs debug bounds drawing into the given canvas.
|
||||
*
|
||||
* @param parent The parent to get child information from, if needed
|
||||
* @param canvas The canvas to draw into
|
||||
*/
|
||||
abstract void debugDraw(FlatViewGroup parent, Canvas canvas);
|
||||
|
||||
abstract float getLeft();
|
||||
|
||||
abstract float getTop();
|
||||
|
||||
abstract float getRight();
|
||||
|
||||
abstract float getBottom();
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
|
||||
/**
|
||||
* Underlying logic which handles draw commands, views and node regions when clipping in a
|
||||
* {@link FlatViewGroup}.
|
||||
*/
|
||||
/* package */ abstract class DrawCommandManager {
|
||||
|
||||
/**
|
||||
* Mount a set of draw commands to this manager. The order the commands are given is the order in
|
||||
* which they should be drawn. If any of the commands are new DrawViews, then mountViews will be
|
||||
* called after by the UIManager.
|
||||
*
|
||||
* @param drawCommands The draw commands to mount.
|
||||
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
|
||||
* @param maxBottom At each index i, the maximum bottom value (or right value in the case of
|
||||
* horizontal clipping) value of all draw commands at or below i.
|
||||
* @param minTop At each index i, the minimum top value (or left value in the case of horizontal
|
||||
* clipping) value of all draw commands at or below i.
|
||||
* @param willMountViews Whether we are going to also receive a mountViews command in this state
|
||||
* cycle.
|
||||
*/
|
||||
abstract void mountDrawCommands(
|
||||
DrawCommand[] drawCommands,
|
||||
SparseIntArray drawViewIndexMap,
|
||||
float[] maxBottom,
|
||||
float[] minTop,
|
||||
boolean willMountViews);
|
||||
|
||||
/**
|
||||
* Add and detach a set of views. The views added here will already have a DrawView passed in
|
||||
* mountDrawCommands.
|
||||
*
|
||||
* @param viewResolver
|
||||
* @param viewsToAdd The views to add, by tag. If this is a new view, this will be reactTag,
|
||||
* otherwise it will be -reactTag. This allows to optimize when we have already attached
|
||||
* views.
|
||||
* @param viewsToDetach The views to detach, by tag. These will all be positive.
|
||||
*/
|
||||
abstract void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach);
|
||||
|
||||
/**
|
||||
* Get the current clipping rect and adjust clipping so that when draw is dispatched we do as
|
||||
* little work as possible.
|
||||
*
|
||||
* @return true if the FlatViewGroup should invalidate.
|
||||
*/
|
||||
abstract boolean updateClippingRect();
|
||||
|
||||
/**
|
||||
* Sets an input rect to match the bounds of our current clipping rect.
|
||||
*
|
||||
* @param outClippingRect Set the out
|
||||
*/
|
||||
abstract void getClippingRect(Rect outClippingRect);
|
||||
|
||||
/**
|
||||
* Return the views that are currently detached, so they can be cleaned up when we are.
|
||||
*
|
||||
* @return A collection of the currently detached views.
|
||||
*/
|
||||
abstract SparseArray<View> getDetachedViews();
|
||||
|
||||
/**
|
||||
* Draw the relevant items. This should do as little work as possible.
|
||||
*
|
||||
* @param canvas The canvas to draw on.
|
||||
*/
|
||||
abstract void draw(Canvas canvas);
|
||||
|
||||
/**
|
||||
* Draws layout bounds for debug.
|
||||
*
|
||||
* @param canvas The canvas to draw on.
|
||||
*/
|
||||
abstract void debugDraw(Canvas canvas);
|
||||
|
||||
/**
|
||||
* Mount node regions, which are the hit boxes of the shadow node children of this FlatViewGroup,
|
||||
* though some may not have a corresponding draw command.
|
||||
*
|
||||
* @param nodeRegions Array of node regions to mount.
|
||||
* @param maxBottom At each index i, the maximum bottom value (or right value in the case of
|
||||
* horizontal clipping) value of all node regions at or below i.
|
||||
* @param minTop At each index i, the minimum top value (or left value in the case of horizontal
|
||||
* clipping) value of all draw commands at or below i.
|
||||
*/
|
||||
abstract void mountNodeRegions(NodeRegion[] nodeRegions, float[] maxBottom, float[] minTop);
|
||||
|
||||
/**
|
||||
* Find a matching node region for a touch.
|
||||
*
|
||||
* @param touchX X coordinate of touch.
|
||||
* @param touchY Y coordinate of touch.
|
||||
* @return Matching node region, or null if none are found.
|
||||
*/
|
||||
abstract @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY);
|
||||
|
||||
/**
|
||||
* Find a matching virtual node region for a touch.
|
||||
*
|
||||
* @param touchX X coordinate of touch.
|
||||
* @param touchY Y coordinate of touch.
|
||||
* @return Matching node region, or null if none are found.
|
||||
*/
|
||||
abstract @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY);
|
||||
|
||||
/**
|
||||
* Event that is fired when a clipped view is dropped.
|
||||
*
|
||||
* @param view the view that is dropped
|
||||
*/
|
||||
abstract void onClippedViewDropped(View view);
|
||||
|
||||
/**
|
||||
* Throw a runtime exception if a view we are trying to attach is already parented.
|
||||
*
|
||||
* @param view The view to check.
|
||||
*/
|
||||
protected static void ensureViewHasNoParent(View view) {
|
||||
ViewParent oldParent = view.getParent();
|
||||
if (oldParent != null) {
|
||||
throw new RuntimeException(
|
||||
"Cannot add view " + view + " to DrawCommandManager while it has a parent " + oldParent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a draw command manager that will clip vertically (The view scrolls up and down).
|
||||
*
|
||||
* @param flatViewGroup FlatViewGroup to use for drawing.
|
||||
* @param drawCommands List of commands to mount.
|
||||
* @return Vertically clipping draw command manager.
|
||||
*/
|
||||
static DrawCommandManager getVerticalClippingInstance(
|
||||
FlatViewGroup flatViewGroup,
|
||||
DrawCommand[] drawCommands) {
|
||||
return new VerticalDrawCommandManager(flatViewGroup, drawCommands);
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
|
||||
/**
|
||||
* Common interface for DrawImageWithDrawee.
|
||||
*/
|
||||
/* package */ interface DrawImage extends AttachDetachListener {
|
||||
/**
|
||||
* Returns true if an image source was assigned to the DrawImage.
|
||||
* A DrawImage with no source will not draw anything.
|
||||
*/
|
||||
boolean hasImageRequest();
|
||||
|
||||
/**
|
||||
* Assigns a new image source to the DrawImage, or null to clear the image request.
|
||||
*/
|
||||
void setSource(Context context, @Nullable ReadableArray sources);
|
||||
|
||||
/**
|
||||
* Assigns a tint color to apply to the image drawn.
|
||||
*/
|
||||
void setTintColor(int tintColor);
|
||||
|
||||
/**
|
||||
* Assigns a scale type to draw to the image with.
|
||||
*/
|
||||
void setScaleType(ScaleType scaleType);
|
||||
|
||||
/**
|
||||
* Returns a scale type to draw to the image with.
|
||||
*/
|
||||
ScaleType getScaleType();
|
||||
|
||||
/**
|
||||
* React tag used for dispatching ImageLoadEvents, or 0 to ignore events.
|
||||
*/
|
||||
void setReactTag(int reactTag);
|
||||
|
||||
void setBorderWidth(float borderWidth);
|
||||
|
||||
float getBorderWidth();
|
||||
|
||||
void setBorderRadius(float borderRadius);
|
||||
|
||||
float getBorderRadius();
|
||||
|
||||
void setBorderColor(int borderColor);
|
||||
|
||||
int getBorderColor();
|
||||
|
||||
void setFadeDuration(int fadeDuration);
|
||||
|
||||
void setProgressiveRenderingEnabled(boolean enabled);
|
||||
}
|
|
@ -1,315 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.net.Uri;
|
||||
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.common.ResizeOptions;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.views.image.GlobalImageLoadListener;
|
||||
import com.facebook.react.views.image.ImageLoadEvent;
|
||||
import com.facebook.react.views.image.ImageResizeMode;
|
||||
import com.facebook.react.views.image.ReactImageView;
|
||||
import com.facebook.react.views.imagehelper.ImageSource;
|
||||
import com.facebook.react.views.imagehelper.MultiSourceHelper;
|
||||
import com.facebook.react.views.imagehelper.MultiSourceHelper.MultiSourceResult;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* DrawImageWithDrawee is a 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, ControllerListener {
|
||||
private static final String LOCAL_FILE_SCHEME = "file";
|
||||
private static final String LOCAL_CONTENT_SCHEME = "content";
|
||||
|
||||
private final List<ImageSource> mSources = new LinkedList<>();
|
||||
private final @Nullable GlobalImageLoadListener mGlobalImageLoadListener;
|
||||
private @Nullable DraweeRequestHelper mRequestHelper;
|
||||
private @Nullable PorterDuffColorFilter mColorFilter;
|
||||
private ScaleType mScaleType = ImageResizeMode.defaultValue();
|
||||
private float mBorderWidth;
|
||||
private float mBorderRadius;
|
||||
private int mBorderColor;
|
||||
private int mReactTag;
|
||||
private boolean mProgressiveRenderingEnabled;
|
||||
private int mFadeDuration = ReactImageView.REMOTE_IMAGE_FADE_DURATION_MS;
|
||||
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
||||
|
||||
public DrawImageWithDrawee(@Nullable GlobalImageLoadListener globalImageLoadListener) {
|
||||
mGlobalImageLoadListener = globalImageLoadListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasImageRequest() {
|
||||
return !mSources.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSource(Context context, @Nullable ReadableArray sources) {
|
||||
mSources.clear();
|
||||
if (sources != null && sources.size() != 0) {
|
||||
// Optimize for the case where we have just one uri, case in which we don't need the sizes
|
||||
if (sources.size() == 1) {
|
||||
ReadableMap source = sources.getMap(0);
|
||||
mSources.add(new ImageSource(context, source.getString("uri")));
|
||||
} else {
|
||||
for (int idx = 0; idx < sources.size(); idx++) {
|
||||
ReadableMap source = sources.getMap(idx);
|
||||
mSources.add(new ImageSource(
|
||||
context,
|
||||
source.getString("uri"),
|
||||
source.getDouble("width"),
|
||||
source.getDouble("height")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 setBorderWidth(float borderWidth) {
|
||||
mBorderWidth = borderWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBorderWidth() {
|
||||
return mBorderWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
mBorderRadius = borderRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBorderRadius() {
|
||||
return mBorderRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderColor(int borderColor) {
|
||||
mBorderColor = borderColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBorderColor() {
|
||||
return mBorderColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFadeDuration(int fadeDuration) {
|
||||
mFadeDuration = fadeDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressiveRenderingEnabled(boolean enabled) {
|
||||
mProgressiveRenderingEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReactTag(int reactTag) {
|
||||
mReactTag = reactTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
if (mRequestHelper != null) {
|
||||
mRequestHelper.getDrawable().draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
||||
mCallback = callback;
|
||||
|
||||
if (mRequestHelper == null) {
|
||||
// this is here to help us debug t12048319, in which we have a null request helper on attach
|
||||
throw new RuntimeException(
|
||||
"No DraweeRequestHelper - width: " +
|
||||
(getRight() - getLeft()) +
|
||||
" - height: " + (getBottom() - getTop() +
|
||||
" - number of sources: " + mSources.size()));
|
||||
}
|
||||
|
||||
GenericDraweeHierarchy hierarchy = mRequestHelper.getHierarchy();
|
||||
|
||||
RoundingParams roundingParams = hierarchy.getRoundingParams();
|
||||
if (shouldDisplayBorder()) {
|
||||
if (roundingParams == null) {
|
||||
roundingParams = new RoundingParams();
|
||||
}
|
||||
|
||||
roundingParams.setBorder(mBorderColor, mBorderWidth);
|
||||
roundingParams.setCornersRadius(mBorderRadius);
|
||||
|
||||
// changes won't take effect until we re-apply rounding params, so do it now.
|
||||
hierarchy.setRoundingParams(roundingParams);
|
||||
} else if (roundingParams != null) {
|
||||
// clear rounding params
|
||||
hierarchy.setRoundingParams(null);
|
||||
}
|
||||
|
||||
hierarchy.setActualImageScaleType(mScaleType);
|
||||
hierarchy.setActualImageColorFilter(mColorFilter);
|
||||
hierarchy.setFadeDuration(mFadeDuration);
|
||||
|
||||
hierarchy.getTopLevelDrawable().setBounds(
|
||||
Math.round(getLeft()),
|
||||
Math.round(getTop()),
|
||||
Math.round(getRight()),
|
||||
Math.round(getBottom()));
|
||||
|
||||
mRequestHelper.attach(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetached() {
|
||||
if (mRequestHelper != null) {
|
||||
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);
|
||||
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
|
||||
}
|
||||
}
|
||||
|
||||
@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_ERROR);
|
||||
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease(String id) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChanged() {
|
||||
super.onBoundsChanged();
|
||||
computeRequestHelper();
|
||||
}
|
||||
|
||||
private void computeRequestHelper() {
|
||||
MultiSourceResult multiSource = MultiSourceHelper.getBestSourceForSize(
|
||||
Math.round(getRight() - getLeft()),
|
||||
Math.round(getBottom() - getTop()),
|
||||
mSources);
|
||||
ImageSource source = multiSource.getBestResult();
|
||||
ImageSource cachedSource = multiSource.getBestResultInCache();
|
||||
if (source == null) {
|
||||
mRequestHelper = null;
|
||||
return;
|
||||
}
|
||||
|
||||
ResizeOptions resizeOptions = null;
|
||||
if (shouldResize(source)) {
|
||||
final int width = (int) (getRight() - getLeft());
|
||||
final int height = (int) (getBottom() - getTop());
|
||||
resizeOptions = new ResizeOptions(width, height);
|
||||
}
|
||||
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(source.getUri())
|
||||
.setResizeOptions(resizeOptions)
|
||||
.setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
|
||||
.build();
|
||||
if (mGlobalImageLoadListener != null) {
|
||||
mGlobalImageLoadListener.onLoadAttempt(source.getUri());
|
||||
}
|
||||
|
||||
ImageRequest cachedImageRequest = null;
|
||||
if (cachedSource != null) {
|
||||
cachedImageRequest = ImageRequestBuilder.newBuilderWithSource(cachedSource.getUri())
|
||||
.setResizeOptions(resizeOptions)
|
||||
.setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
|
||||
.build();
|
||||
}
|
||||
mRequestHelper = new
|
||||
DraweeRequestHelper(Assertions.assertNotNull(imageRequest), cachedImageRequest, this);
|
||||
}
|
||||
|
||||
private boolean shouldDisplayBorder() {
|
||||
return mBorderColor != 0 || mBorderRadius >= 0.5f;
|
||||
}
|
||||
|
||||
private static boolean shouldResize(ImageSource imageSource) {
|
||||
// Resizing is inferior to scaling. See http://frescolib.org/docs/resizing-rotating.html
|
||||
// We resize here only for images likely to be from the device's camera, where the app developer
|
||||
// has no control over the original size
|
||||
Uri uri = imageSource.getUri();
|
||||
String type = uri == null ? null : uri.getScheme();
|
||||
// one day, we can replace this with what non-Nodes does, which is:
|
||||
// UriUtil.isLocalContentUri || UriUtil.isLocalFileUri
|
||||
// not doing this just to save including eyt another BUCK dependency
|
||||
return LOCAL_FILE_SCHEME.equals(type) || LOCAL_CONTENT_SCHEME.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDebugDrawHighlight(Canvas canvas) {
|
||||
if (mCallback != null) {
|
||||
debugDrawCautionHighlight(canvas, "Invalidate Drawee");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.text.Layout;
|
||||
|
||||
import com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil;
|
||||
|
||||
/**
|
||||
* DrawTextLayout is a DrawCommand that draw {@link Layout}.
|
||||
*/
|
||||
/* package */ final class DrawTextLayout extends AbstractDrawCommand {
|
||||
|
||||
private Layout mLayout;
|
||||
private float mLayoutWidth;
|
||||
private float mLayoutHeight;
|
||||
|
||||
/* package */ DrawTextLayout(Layout layout) {
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a new {@link Layout} to draw.
|
||||
*/
|
||||
public void setLayout(Layout layout) {
|
||||
mLayout = layout;
|
||||
mLayoutWidth = layout.getWidth();
|
||||
mLayoutHeight = LayoutMeasureUtil.getHeight(layout);
|
||||
}
|
||||
|
||||
public Layout getLayout() {
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
public float getLayoutWidth() {
|
||||
// mLayout.getWidth() doesn't return correct width of the text Layout
|
||||
return mLayoutWidth;
|
||||
}
|
||||
|
||||
public float getLayoutHeight() {
|
||||
return mLayoutHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
float left = getLeft();
|
||||
float top = getTop();
|
||||
|
||||
canvas.translate(left, top);
|
||||
mLayout.draw(canvas);
|
||||
canvas.translate(-left, -top);
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
|
||||
/* package */ final class DrawView extends AbstractDrawCommand {
|
||||
public static final DrawView[] EMPTY_ARRAY = new DrawView[0];
|
||||
// the minimum rounded clipping value before we actually do rounded clipping
|
||||
/* package */ static final float MINIMUM_ROUNDED_CLIPPING_VALUE = 0.5f;
|
||||
private final RectF TMP_RECT = new RectF();
|
||||
|
||||
/* package */ final int reactTag;
|
||||
// Indicates whether this DrawView has been previously mounted to a clipping FlatViewGroup. This
|
||||
// lets us know that the bounds haven't changed, as a bounds change would trigger a new DrawView,
|
||||
// which will set this to false for the new DrawView. This is safe, despite the dual access with
|
||||
// FlatViewGroup, because the FlatViewGroup copy is only ever modified by the FlatViewGroup.
|
||||
// Changing how this boolean is used should be handled with caution, as race conditions are the
|
||||
// quickest way to create unreproducible super bugs.
|
||||
/* package */ boolean mWasMounted;
|
||||
|
||||
// the clipping radius - if this is greater than MINIMUM_ROUNDED_CLIPPING_VALUE, we clip using
|
||||
// a rounded path, otherwise we clip in a rectangular fashion.
|
||||
private float mClipRadius;
|
||||
|
||||
// the path to clip against if we're doing path clipping for rounded borders.
|
||||
@Nullable private Path mPath;
|
||||
|
||||
// These should only ever be set from within the DrawView, they serve to provide clipping bounds
|
||||
// for FlatViewGroups, which have strange clipping when it comes to overflow: visible. They are
|
||||
// left package protected to speed up direct access. For overflow visible, these are the adjusted
|
||||
// bounds while taking overflowing elements into account, other wise they are just the regular
|
||||
// bounds of the view.
|
||||
/* package */ float mLogicalLeft;
|
||||
/* package */ float mLogicalTop;
|
||||
/* package */ float mLogicalRight;
|
||||
/* package */ float mLogicalBottom;
|
||||
|
||||
public DrawView(int reactTag) {
|
||||
this.reactTag = reactTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to updateBoundsAndFreeze, but thread safe as the mounting flag is modified on the UI
|
||||
* thread.
|
||||
*
|
||||
* @return A DrawView with the passed bounds and clipping bounds. If we can use the same
|
||||
* DrawView, it will just be this, otherwise it will be a frozen copy.
|
||||
*/
|
||||
public DrawView collectDrawView(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float logicalLeft,
|
||||
float logicalTop,
|
||||
float logicalRight,
|
||||
float logicalBottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom,
|
||||
float clipRadius) {
|
||||
if (!isFrozen()) {
|
||||
// We haven't collected this draw view yet, so we can just set everything.
|
||||
setBounds(left, top, right, bottom);
|
||||
setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
|
||||
setClipRadius(clipRadius);
|
||||
setLogicalBounds(logicalLeft, logicalTop, logicalRight, logicalBottom);
|
||||
freeze();
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean boundsMatch = boundsMatch(left, top, right, bottom);
|
||||
boolean clipBoundsMatch = clipBoundsMatch(clipLeft, clipTop, clipRight, clipBottom);
|
||||
boolean clipRadiusMatch = mClipRadius == clipRadius;
|
||||
boolean logicalBoundsMatch =
|
||||
logicalBoundsMatch(logicalLeft, logicalTop, logicalRight, logicalBottom);
|
||||
|
||||
// See if we can reuse the draw view.
|
||||
if (boundsMatch && clipBoundsMatch && clipRadiusMatch && logicalBoundsMatch) {
|
||||
return this;
|
||||
}
|
||||
|
||||
DrawView drawView = (DrawView) mutableCopy();
|
||||
|
||||
if (!boundsMatch) {
|
||||
drawView.setBounds(left, top, right, bottom);
|
||||
}
|
||||
|
||||
if (!clipBoundsMatch) {
|
||||
drawView.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
|
||||
}
|
||||
|
||||
if (!logicalBoundsMatch) {
|
||||
drawView.setLogicalBounds(logicalLeft, logicalTop, logicalRight, logicalBottom);
|
||||
}
|
||||
|
||||
if (!clipRadiusMatch || !boundsMatch) {
|
||||
// If the bounds change, we need to update the clip path.
|
||||
drawView.setClipRadius(clipRadius);
|
||||
}
|
||||
|
||||
// It is very important that we unset this, as our spec is that newly created DrawViews
|
||||
// are handled differently by the FlatViewGroup. This is needed because clone() maintains
|
||||
// the previous state.
|
||||
drawView.mWasMounted = false;
|
||||
|
||||
drawView.freeze();
|
||||
|
||||
return drawView;
|
||||
}
|
||||
|
||||
private boolean logicalBoundsMatch(float left, float top, float right, float bottom) {
|
||||
return left == mLogicalLeft && top == mLogicalTop &&
|
||||
right == mLogicalRight && bottom == mLogicalBottom;
|
||||
}
|
||||
|
||||
private void setLogicalBounds(float left, float top, float right, float bottom) {
|
||||
// Do rounding up front and off of the UI thread.
|
||||
mLogicalLeft = left;
|
||||
mLogicalTop = top;
|
||||
mLogicalRight = right;
|
||||
mLogicalBottom = bottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(FlatViewGroup parent, Canvas canvas) {
|
||||
onPreDraw(parent, canvas);
|
||||
if (mNeedsClipping || mClipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
canvas.save(Canvas.CLIP_SAVE_FLAG);
|
||||
applyClipping(canvas);
|
||||
parent.drawNextChild(canvas);
|
||||
canvas.restore();
|
||||
} else {
|
||||
parent.drawNextChild(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clip radius. Should only be called when the clip radius is first set or when it
|
||||
* changes, in order to avoid extra work.
|
||||
*
|
||||
* @param clipRadius The new clip radius.
|
||||
*/
|
||||
void setClipRadius(float clipRadius) {
|
||||
mClipRadius = clipRadius;
|
||||
if (clipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
// update the path that we'll clip based on
|
||||
updateClipPath();
|
||||
} else {
|
||||
mPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the path with which we'll clip this view
|
||||
*/
|
||||
private void updateClipPath() {
|
||||
mPath = new Path();
|
||||
|
||||
TMP_RECT.set(
|
||||
getLeft(),
|
||||
getTop(),
|
||||
getRight(),
|
||||
getBottom());
|
||||
|
||||
// set the path
|
||||
mPath.addRoundRect(
|
||||
TMP_RECT,
|
||||
mClipRadius,
|
||||
mClipRadius,
|
||||
Path.Direction.CW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyClipping(Canvas canvas) {
|
||||
// only clip using a path if our radius is greater than some minimum threshold, because
|
||||
// clipPath is more expensive than clipRect.
|
||||
if (mClipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
canvas.clipPath(mPath);
|
||||
} else {
|
||||
super.applyClipping(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
// no op as we override draw.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDebugDraw(FlatViewGroup parent, Canvas canvas) {
|
||||
parent.debugDrawNextChild(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDebugDrawHighlight(Canvas canvas) {
|
||||
if (mPath != null) {
|
||||
debugDrawWarningHighlight(canvas, "borderRadius: " + mClipRadius);
|
||||
} else if (!boundsMatch(mLogicalLeft, mLogicalTop, mLogicalRight, mLogicalBottom)) {
|
||||
StringBuilder warn = new StringBuilder("Overflow: { ");
|
||||
String[] names = { "left: ", "top: ", "right: ", "bottom: "};
|
||||
int i = 0;
|
||||
float[] offsets = new float[4];
|
||||
offsets[i++] = getLeft() - mLogicalLeft;
|
||||
offsets[i++] = getTop() - mLogicalTop;
|
||||
offsets[i++] = mLogicalRight - getRight();
|
||||
offsets[i++] = mLogicalBottom - getBottom();
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (offsets[i] != 0f) {
|
||||
warn.append(names[i]);
|
||||
warn.append(offsets[i]);
|
||||
warn.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
warn.append("}");
|
||||
|
||||
debugDrawCautionHighlight(canvas, warn.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
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;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/* package */ final class DraweeRequestHelper {
|
||||
|
||||
private static GenericDraweeHierarchyBuilder sHierarchyBuilder;
|
||||
private static AbstractDraweeControllerBuilder sControllerBuilder;
|
||||
|
||||
/* package */ static void setResources(Resources resources) {
|
||||
sHierarchyBuilder = new GenericDraweeHierarchyBuilder(resources);
|
||||
}
|
||||
|
||||
/* package */ static void setDraweeControllerBuilder(AbstractDraweeControllerBuilder builder) {
|
||||
sControllerBuilder = builder;
|
||||
}
|
||||
|
||||
private final DraweeController mDraweeController;
|
||||
private int mAttachCounter;
|
||||
|
||||
/* package */ DraweeRequestHelper(
|
||||
ImageRequest imageRequest,
|
||||
@Nullable ImageRequest cachedImageRequest,
|
||||
ControllerListener listener) {
|
||||
AbstractDraweeControllerBuilder controllerBuilder = sControllerBuilder
|
||||
.setImageRequest(imageRequest)
|
||||
.setCallerContext(RCTImageView.getCallerContext())
|
||||
.setControllerListener(listener);
|
||||
|
||||
if (cachedImageRequest != null) {
|
||||
controllerBuilder.setLowResImageRequest(cachedImageRequest);
|
||||
}
|
||||
DraweeController controller = controllerBuilder.build();
|
||||
|
||||
controller.setHierarchy(sHierarchyBuilder.build());
|
||||
|
||||
mDraweeController = controller;
|
||||
}
|
||||
|
||||
/* package */ void attach(FlatViewGroup.InvalidateCallback callback) {
|
||||
++mAttachCounter;
|
||||
if (mAttachCounter == 1) {
|
||||
getDrawable().setCallback(callback.get());
|
||||
mDraweeController.onAttach();
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void detach() {
|
||||
--mAttachCounter;
|
||||
if (mAttachCounter == 0) {
|
||||
mDraweeController.onDetach();
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ GenericDraweeHierarchy getHierarchy() {
|
||||
return (GenericDraweeHierarchy) Assertions.assumeNotNull(mDraweeController.getHierarchy());
|
||||
}
|
||||
|
||||
/* package */ Drawable getDrawable() {
|
||||
return getHierarchy().getTopLevelDrawable();
|
||||
}
|
||||
}
|
|
@ -1,198 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
/**
|
||||
* Diffing scope stack class that supports 3 main operations: start(), add() an element and
|
||||
* finish().
|
||||
*
|
||||
* When started, it takes a baseline array to compare to. When adding a new element, it checks
|
||||
* whether a corresponding element in baseline array is the same. On finish(), it will return null
|
||||
* if baseline array contains exactly the same elements that were added with a sequence of add()
|
||||
* calls, or a new array of the recorded elements:
|
||||
*
|
||||
* Example 1:
|
||||
* -----
|
||||
* start([A])
|
||||
* add(A)
|
||||
* finish() -> null (because [A] == [A])
|
||||
*
|
||||
* Example 2:
|
||||
* ----
|
||||
* start([A])
|
||||
* add(B)
|
||||
* finish() -> [B] (because [A] != [B])
|
||||
*
|
||||
* Example 3:
|
||||
* ----
|
||||
* start([A])
|
||||
* add(B)
|
||||
* add(A)
|
||||
* finish() -> [B, A] (because [B, A] != [A])
|
||||
*
|
||||
* Example 4:
|
||||
* ----
|
||||
* start([A, B])
|
||||
* add(B)
|
||||
* add(A)
|
||||
* finish() -> [B, A] (because [B, A] != [A, B])
|
||||
*
|
||||
* It is important that start/finish can be nested:
|
||||
* ----
|
||||
* start([A])
|
||||
* add(A)
|
||||
* start([B])
|
||||
* add(B)
|
||||
* finish() -> null
|
||||
* add(C)
|
||||
* finish() -> [A, C]
|
||||
*
|
||||
* StateBuilder is using this class to check if e.g. a DrawCommand list for a given View needs to be
|
||||
* updated.
|
||||
*/
|
||||
/* package */ final class ElementsList<E> {
|
||||
|
||||
private static final class Scope {
|
||||
Object[] elements;
|
||||
int index;
|
||||
int size;
|
||||
}
|
||||
|
||||
// List of scopes. These are never cleared, but instead recycled when a new scope is needed at
|
||||
// a given depth.
|
||||
private final ArrayList<Scope> mScopesStack = new ArrayList<>();
|
||||
// Working list of all new elements we are gathering across scopes. Whenever we get a call to
|
||||
// finish() we pop the new elements off the collection, either discarding them if there was no
|
||||
// change from the base or accumulating and returning them as a list of new elements.
|
||||
private final ArrayDeque<E> mElements = new ArrayDeque<>();
|
||||
private final E[] mEmptyArray;
|
||||
private Scope mCurrentScope = null;
|
||||
private int mScopeIndex = 0;
|
||||
|
||||
public ElementsList(E[] emptyArray) {
|
||||
mEmptyArray = emptyArray;
|
||||
mScopesStack.add(mCurrentScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new scope.
|
||||
*/
|
||||
public void start(Object[] elements) {
|
||||
pushScope();
|
||||
|
||||
Scope scope = getCurrentScope();
|
||||
scope.elements = elements;
|
||||
scope.index = 0;
|
||||
scope.size = mElements.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish current scope, returning null if there were no changes recorded, or a new array
|
||||
* containing all the newly recorded elements otherwise.
|
||||
*/
|
||||
public E[] finish() {
|
||||
Scope scope = getCurrentScope();
|
||||
popScope();
|
||||
|
||||
E[] result = null;
|
||||
int size = mElements.size() - scope.size;
|
||||
if (scope.index != scope.elements.length) {
|
||||
result = extractElements(size);
|
||||
} else {
|
||||
// downsize
|
||||
for (int i = 0; i < size; ++i) {
|
||||
mElements.pollLast();
|
||||
}
|
||||
}
|
||||
|
||||
// To prevent resource leaks.
|
||||
scope.elements = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new element to the list. This method can be optimized to avoid inserts on same
|
||||
* elements, but would involve copying from scope.elements when we extract elements.
|
||||
*/
|
||||
public void add(E element) {
|
||||
Scope scope = getCurrentScope();
|
||||
|
||||
if (scope.index < scope.elements.length &&
|
||||
scope.elements[scope.index] == element) {
|
||||
++scope.index;
|
||||
} else {
|
||||
scope.index = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
mElements.add(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all references to elements in our new stack to null to avoid memory leaks.
|
||||
*/
|
||||
public void clear() {
|
||||
if (getCurrentScope() != null) {
|
||||
throw new RuntimeException("Must call finish() for every start() call being made.");
|
||||
}
|
||||
mElements.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts last size elements into an array. Used to extract our new array of items from our
|
||||
* stack when the new items != old items.
|
||||
*/
|
||||
private E[] extractElements(int size) {
|
||||
if (size == 0) {
|
||||
// avoid allocating empty array
|
||||
return mEmptyArray;
|
||||
}
|
||||
|
||||
E[] elements = (E[]) Array.newInstance(mEmptyArray.getClass().getComponentType(), size);
|
||||
for (int i = size - 1; i >= 0; --i) {
|
||||
elements[i] = mElements.pollLast();
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current scope in a stack.
|
||||
*/
|
||||
private void pushScope() {
|
||||
++mScopeIndex;
|
||||
if (mScopeIndex == mScopesStack.size()) {
|
||||
// We reached a new deepest scope, we need to create a scope for this depth.
|
||||
mCurrentScope = new Scope();
|
||||
mScopesStack.add(mCurrentScope);
|
||||
} else {
|
||||
// We have had a scope at this depth before, lets recycle it.
|
||||
mCurrentScope = mScopesStack.get(mScopeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores last saved current scope. Doesn't actually remove the scope, as scopes are
|
||||
* recycled.
|
||||
*/
|
||||
private void popScope() {
|
||||
--mScopeIndex;
|
||||
mCurrentScope = mScopesStack.get(mScopeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current scope.
|
||||
*/
|
||||
private Scope getCurrentScope() {
|
||||
return mCurrentScope;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.react.uimanager.BaseViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.views.art.ARTSurfaceView;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
|
||||
public class FlatARTSurfaceViewManager extends
|
||||
BaseViewManager<ARTSurfaceView, FlatARTSurfaceViewShadowNode> {
|
||||
|
||||
/* package */ static final String REACT_CLASS = "ARTSurfaceView";
|
||||
|
||||
private static final YogaMeasureFunction MEASURE_FUNCTION = new YogaMeasureFunction() {
|
||||
@Override
|
||||
public long measure(
|
||||
YogaNode node,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode) {
|
||||
throw new IllegalStateException("SurfaceView should have explicit width and height set");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlatARTSurfaceViewShadowNode createShadowNodeInstance() {
|
||||
FlatARTSurfaceViewShadowNode node = new FlatARTSurfaceViewShadowNode();
|
||||
node.setMeasureFunction(MEASURE_FUNCTION);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<FlatARTSurfaceViewShadowNode> getShadowNodeClass() {
|
||||
return FlatARTSurfaceViewShadowNode.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ARTSurfaceView createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ARTSurfaceView(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExtraData(ARTSurfaceView root, Object extraData) {
|
||||
root.setSurfaceTextureListener((FlatARTSurfaceViewShadowNode) extraData);
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.views.art.ARTVirtualNode;
|
||||
import com.facebook.yoga.YogaValue;
|
||||
import com.facebook.yoga.YogaUnit;
|
||||
|
||||
/* package */ class FlatARTSurfaceViewShadowNode extends FlatShadowNode
|
||||
implements AndroidView, TextureView.SurfaceTextureListener {
|
||||
private boolean mPaddingChanged = false;
|
||||
private @Nullable Surface mSurface;
|
||||
|
||||
/* package */ FlatARTSurfaceViewShadowNode() {
|
||||
forceMountToView();
|
||||
forceMountChildrenToView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtualAnchor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
|
||||
super.onCollectExtraUpdates(uiUpdater);
|
||||
drawOutput();
|
||||
uiUpdater.enqueueUpdateExtraData(getReactTag(), this);
|
||||
}
|
||||
|
||||
private void drawOutput() {
|
||||
if (mSurface == null || !mSurface.isValid()) {
|
||||
markChildrenUpdatesSeen(this);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Canvas canvas = mSurface.lockCanvas(null);
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
|
||||
Paint paint = new Paint();
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
ARTVirtualNode child = (ARTVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, 1f);
|
||||
child.markUpdateSeen();
|
||||
}
|
||||
|
||||
if (mSurface == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSurface.unlockCanvasAndPost(canvas);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
Log.e(ReactConstants.TAG, e.getClass().getSimpleName() + " in Surface.unlockCanvasAndPost");
|
||||
}
|
||||
}
|
||||
|
||||
private void markChildrenUpdatesSeen(ReactShadowNode shadowNode) {
|
||||
for (int i = 0; i < shadowNode.getChildCount(); i++) {
|
||||
ReactShadowNode child = shadowNode.getChildAt(i);
|
||||
child.markUpdateSeen();
|
||||
markChildrenUpdatesSeen(child);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaddingChanged() {
|
||||
return mPaddingChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPaddingChanged() {
|
||||
mPaddingChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int spacingType, float padding) {
|
||||
YogaValue current = getStylePadding(spacingType);
|
||||
if (current.unit != YogaUnit.POINT || current.value != padding) {
|
||||
super.setPadding(spacingType, padding);
|
||||
mPaddingChanged = true;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaddingPercent(int spacingType, float percent) {
|
||||
YogaValue current = getStylePadding(spacingType);
|
||||
if (current.unit != YogaUnit.PERCENT || current.value != percent) {
|
||||
super.setPadding(spacingType, percent);
|
||||
mPaddingChanged = true;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||
mSurface = new Surface(surface);
|
||||
drawOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||
surface.release();
|
||||
mSurface = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
/**
|
||||
* Helper interface to provide measuring of FlatViewGroup when needed. We don't override onMeasure
|
||||
* for FlatViewGroup, which means that draw commands don't contributed to the measured width and
|
||||
* height. This allows us to expose our calculated dimensions taking into account draw commands,
|
||||
* without changing the visibility of the FlatViewGroup.
|
||||
*/
|
||||
public interface FlatMeasuredViewGroup {
|
||||
/**
|
||||
* @return A rect consisting of the left, top, right, and bottommost edge among all children,
|
||||
* including draw commands.
|
||||
*/
|
||||
Rect measureWithCommands();
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.View.MeasureSpec;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
||||
import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
|
||||
/**
|
||||
* FlatNativeViewHierarchyManager is the only class that performs View manipulations. All of this
|
||||
* class methods can only be called from UI thread by {@link FlatUIViewOperationQueue}.
|
||||
*/
|
||||
/* package */ final class FlatNativeViewHierarchyManager extends NativeViewHierarchyManager
|
||||
implements ViewResolver {
|
||||
|
||||
/* package */ FlatNativeViewHierarchyManager(ViewManagerRegistry viewManagers) {
|
||||
super(viewManagers, new FlatRootViewManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int reactTag) {
|
||||
return super.resolveView(reactTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRootView(
|
||||
int tag,
|
||||
SizeMonitoringFrameLayout view,
|
||||
ThemedReactContext themedContext) {
|
||||
FlatViewGroup root = new FlatViewGroup(themedContext);
|
||||
view.addView(root);
|
||||
|
||||
// When unmounting, ReactInstanceManager.detachViewFromInstance() will check id of the
|
||||
// top-level View (SizeMonitoringFrameLayout) and pass it back to JS. We want that View's id to
|
||||
// be set, otherwise NativeViewHierarchyManager will not be able to cleanup properly.
|
||||
view.setId(tag);
|
||||
|
||||
addRootViewGroup(tag, root, themedContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates DrawCommands and AttachDetachListeners of a FlatViewGroup specified by a reactTag.
|
||||
*
|
||||
* @param reactTag reactTag to lookup FlatViewGroup by
|
||||
* @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,
|
||||
@Nullable DrawCommand[] drawCommands,
|
||||
@Nullable AttachDetachListener[] listeners,
|
||||
@Nullable NodeRegion[] nodeRegions) {
|
||||
FlatViewGroup view = (FlatViewGroup) resolveView(reactTag);
|
||||
if (drawCommands != null) {
|
||||
view.mountDrawCommands(drawCommands);
|
||||
}
|
||||
if (listeners != null) {
|
||||
view.mountAttachDetachListeners(listeners);
|
||||
}
|
||||
if (nodeRegions != null) {
|
||||
view.mountNodeRegions(nodeRegions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates DrawCommands and AttachDetachListeners of a clipping FlatViewGroup specified by a
|
||||
* reactTag.
|
||||
*
|
||||
* @param reactTag The react tag to lookup FlatViewGroup by.
|
||||
* @param drawCommands If non-null, new draw commands to execute during the drawing.
|
||||
* @param drawViewIndexMap Mapping of react tags to the index of the corresponding DrawView
|
||||
* command in the draw command array.
|
||||
* @param commandMaxBot At each index i, the maximum bottom value (or right value in the case of
|
||||
* horizontal clipping) value of all draw commands at or below i.
|
||||
* @param commandMinTop At each index i, the minimum top value (or left value in the case of
|
||||
* horizontal clipping) value of all draw commands at or below i.
|
||||
* @param listeners If non-null, new attach-detach listeners.
|
||||
* @param nodeRegions Node regions to mount.
|
||||
* @param regionMaxBot At each index i, the maximum bottom value (or right value in the case of
|
||||
* horizontal clipping) value of all node regions at or below i.
|
||||
* @param regionMinTop At each index i, the minimum top value (or left value in the case of
|
||||
* horizontal clipping) value of all draw commands at or below i.
|
||||
* @param willMountViews Whether we are going to also send a mountViews command in this state
|
||||
* cycle.
|
||||
*/
|
||||
/* package */ void updateClippingMountState(
|
||||
int reactTag,
|
||||
@Nullable DrawCommand[] drawCommands,
|
||||
SparseIntArray drawViewIndexMap,
|
||||
float[] commandMaxBot,
|
||||
float[] commandMinTop,
|
||||
@Nullable AttachDetachListener[] listeners,
|
||||
@Nullable NodeRegion[] nodeRegions,
|
||||
float[] regionMaxBot,
|
||||
float[] regionMinTop,
|
||||
boolean willMountViews) {
|
||||
FlatViewGroup view = (FlatViewGroup) resolveView(reactTag);
|
||||
if (drawCommands != null) {
|
||||
view.mountClippingDrawCommands(
|
||||
drawCommands,
|
||||
drawViewIndexMap,
|
||||
commandMaxBot,
|
||||
commandMinTop,
|
||||
willMountViews);
|
||||
}
|
||||
if (listeners != null) {
|
||||
view.mountAttachDetachListeners(listeners);
|
||||
}
|
||||
if (nodeRegions != null) {
|
||||
view.mountClippingNodeRegions(nodeRegions, regionMaxBot, regionMinTop);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void updateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
|
||||
View view = resolveView(reactTag);
|
||||
if (view instanceof FlatViewGroup) {
|
||||
((FlatViewGroup) view).mountViews(this, viewsToAdd, viewsToDetach);
|
||||
return;
|
||||
}
|
||||
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(reactTag);
|
||||
List<View> listOfViews = new ArrayList<>(viewsToAdd.length);
|
||||
|
||||
// batch the set of additions - some view managers can take advantage of the batching to
|
||||
// decrease operations, etc.
|
||||
for (int viewIdToAdd : viewsToAdd) {
|
||||
int tag = Math.abs(viewIdToAdd);
|
||||
listOfViews.add(resolveView(tag));
|
||||
}
|
||||
viewManager.addViews(viewGroup, listOfViews);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates View bounds, possibly re-measuring and re-layouting it if the size changed.
|
||||
*
|
||||
* @param reactTag reactTag to lookup a View by
|
||||
* @param left left coordinate relative to parent
|
||||
* @param top top coordinate relative to parent
|
||||
* @param right right coordinate relative to parent
|
||||
* @param bottom bottom coordinate relative to parent
|
||||
*/
|
||||
/* package */ void updateViewBounds(int reactTag, int left, int top, int right, int bottom) {
|
||||
View view = resolveView(reactTag);
|
||||
int width = right - left;
|
||||
int height = bottom - top;
|
||||
if (view.getWidth() != width || view.getHeight() != height) {
|
||||
// size changed, we need to measure and layout the View
|
||||
view.measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
||||
view.layout(left, top, right, bottom);
|
||||
} else {
|
||||
// same size, only location changed, there is a faster route.
|
||||
view.offsetLeftAndRight(left - view.getLeft());
|
||||
view.offsetTopAndBottom(top - view.getTop());
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void setPadding(
|
||||
int reactTag,
|
||||
int paddingLeft,
|
||||
int paddingTop,
|
||||
int paddingRight,
|
||||
int paddingBottom) {
|
||||
resolveView(reactTag).setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
|
||||
}
|
||||
|
||||
/* package */ void dropViews(SparseIntArray viewsToDrop) {
|
||||
for (int i = 0, count = viewsToDrop.size(); i < count; i++) {
|
||||
int viewToDrop = viewsToDrop.keyAt(i);
|
||||
View view = null;
|
||||
if (viewToDrop > 0) {
|
||||
try {
|
||||
view = resolveView(viewToDrop);
|
||||
dropView(view);
|
||||
} catch (Exception e) {
|
||||
// the view is already dropped, nothing we can do
|
||||
}
|
||||
} else {
|
||||
// Root views are noted with a negative tag from StateBuilder.
|
||||
removeRootView(-viewToDrop);
|
||||
}
|
||||
|
||||
int parentTag = viewsToDrop.valueAt(i);
|
||||
// this only happens for clipped, non-root views - clipped because there is no parent, and
|
||||
// not a root view (because we explicitly pass -1 for root views).
|
||||
if (parentTag > 0 && view != null && view.getParent() == null) {
|
||||
// this can only happen if the parent exists (if the parent were removed first, it'd also
|
||||
// remove the child, so trying to explicitly remove the child afterwards would crash at
|
||||
// the resolveView call above) - we also explicitly check for a null parent, implying that
|
||||
// we are either clipped (or that we already removed the child from its parent, in which
|
||||
// case this will essentially be a no-op).
|
||||
View parent = resolveView(parentTag);
|
||||
if (parent instanceof FlatViewGroup) {
|
||||
((FlatViewGroup) parent).onViewDropped(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropView(View view) {
|
||||
super.dropView(view);
|
||||
|
||||
// As a result of removeClippedSubviews, some views have strong references but are not attached
|
||||
// to a parent. consequently, when the parent gets removed, these Views don't get cleaned up,
|
||||
// because they aren't children (they also aren't removed from mTagsToViews, thus causing a
|
||||
// leak). To solve this, we ask for said detached views and explicitly drop them.
|
||||
if (view instanceof FlatViewGroup) {
|
||||
FlatViewGroup flatViewGroup = (FlatViewGroup) view;
|
||||
if (flatViewGroup.getRemoveClippedSubviews()) {
|
||||
SparseArray<View> detachedViews = flatViewGroup.getDetachedViews();
|
||||
for (int i = 0, size = detachedViews.size(); i < size; i++) {
|
||||
View detachedChild = detachedViews.valueAt(i);
|
||||
try {
|
||||
dropView(detachedChild);
|
||||
} catch (Exception e) {
|
||||
// if the view is already dropped, ignore any exceptions
|
||||
// in reality, we should find out the edge cases that cause
|
||||
// this to happen and properly fix them.
|
||||
}
|
||||
// trigger onDetachedFromWindow and clean up this detached/clipped view
|
||||
flatViewGroup.removeDetachedView(detachedChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void detachAllChildrenFromViews(int[] viewsToDetachAllChildrenFrom) {
|
||||
for (int viewTag : viewsToDetachAllChildrenFrom) {
|
||||
View view = resolveView(viewTag);
|
||||
if (view instanceof FlatViewGroup) {
|
||||
((FlatViewGroup) view).detachAllViewsFromParent();
|
||||
continue;
|
||||
}
|
||||
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(viewTag);
|
||||
viewManager.removeAllViews(viewGroup);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.yoga.YogaUnit;
|
||||
import com.facebook.yoga.YogaValue;
|
||||
|
||||
/**
|
||||
* FlatReactModalShadowNode
|
||||
*
|
||||
* This is a Nodes specific shadow node for modals. This is required because we wrap any
|
||||
* non-FlatShadowNode node in a NativeViewWrapper. In the case of a modal shadow node, we need
|
||||
* to treat it as its own node so that we can add the custom measurement behavior that is there
|
||||
* in the non-Nodes version when we add a child.
|
||||
*
|
||||
* {@see {@link com.facebook.react.views.modal.ModalHostShadowNode}}
|
||||
*/
|
||||
class FlatReactModalShadowNode extends FlatShadowNode implements AndroidView {
|
||||
|
||||
private final Point mMinPoint = new Point();
|
||||
private final Point mMaxPoint = new Point();
|
||||
private boolean mPaddingChanged;
|
||||
|
||||
FlatReactModalShadowNode() {
|
||||
forceMountToView();
|
||||
forceMountChildrenToView();
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to set the styleWidth and styleHeight of the one child (represented by the <View/>
|
||||
* within the <RCTModalHostView/> in Modal.js. This needs to fill the entire window.
|
||||
*/
|
||||
@Override
|
||||
@TargetApi(16)
|
||||
public void addChildAt(ReactShadowNodeImpl child, int i) {
|
||||
super.addChildAt(child, i);
|
||||
|
||||
Context context = getThemedContext();
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
// getCurrentSizeRange will return the min and max width and height that the window can be
|
||||
display.getCurrentSizeRange(mMinPoint, mMaxPoint);
|
||||
|
||||
int width, height;
|
||||
int rotation = display.getRotation();
|
||||
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
|
||||
// If we are vertical the width value comes from min width and height comes from max height
|
||||
width = mMinPoint.x;
|
||||
height = mMaxPoint.y;
|
||||
} else {
|
||||
// If we are horizontal the width value comes from max width and height comes from min height
|
||||
width = mMaxPoint.x;
|
||||
height = mMinPoint.y;
|
||||
}
|
||||
child.setStyleWidth(width);
|
||||
child.setStyleHeight(height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaddingChanged() {
|
||||
return mPaddingChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPaddingChanged() {
|
||||
mPaddingChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int spacingType, float padding) {
|
||||
YogaValue current = getStylePadding(spacingType);
|
||||
if (current.unit != YogaUnit.POINT || current.value != padding) {
|
||||
super.setPadding(spacingType, padding);
|
||||
mPaddingChanged = true;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaddingPercent(int spacingType, float percent) {
|
||||
YogaValue current = getStylePadding(spacingType);
|
||||
if (current.unit != YogaUnit.PERCENT || current.value != percent) {
|
||||
super.setPadding(spacingType, percent);
|
||||
mPaddingChanged = true;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
/**
|
||||
* Root node of the shadow node hierarchy. Currently, the only node that can actually map to a View.
|
||||
*/
|
||||
/* package */ final class FlatRootShadowNode extends FlatShadowNode {
|
||||
|
||||
/* package */ FlatRootShadowNode() {
|
||||
forceMountToView();
|
||||
signalBackingViewIsCreated();
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.react.uimanager.RootViewManager;
|
||||
|
||||
/* package */ class FlatRootViewManager extends RootViewManager {
|
||||
|
||||
@Override
|
||||
public void removeAllViews(ViewGroup parent) {
|
||||
parent.removeAllViewsInLayout();
|
||||
}
|
||||
}
|
|
@ -1,570 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.OnLayoutEvent;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* FlatShadowNode is a base class for all shadow node used in FlatUIImplementation. It extends
|
||||
* {@link LayoutShadowNode} by adding an ability to prepare DrawCommands off the UI thread.
|
||||
*/
|
||||
/* package */ class FlatShadowNode extends LayoutShadowNode {
|
||||
|
||||
/* package */ static final FlatShadowNode[] EMPTY_ARRAY = new FlatShadowNode[0];
|
||||
|
||||
private static final String PROP_OPACITY = "opacity";
|
||||
private static final String PROP_RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
|
||||
private static final String PROP_ACCESSIBILITY_LABEL = "accessibilityLabel";
|
||||
private static final String PROP_ACCESSIBILITY_COMPONENT_TYPE = "accessibilityComponentType";
|
||||
private static final String PROP_ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
|
||||
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";
|
||||
private static final String PROP_TEST_ID = "testID";
|
||||
private static final String PROP_TRANSFORM = "transform";
|
||||
protected static final String PROP_REMOVE_CLIPPED_SUBVIEWS =
|
||||
ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS;
|
||||
protected static final String PROP_HORIZONTAL = "horizontal";
|
||||
private static final Rect LOGICAL_OFFSET_EMPTY = new Rect();
|
||||
// When we first initialize a backing view, we create a view we are going to throw away anyway,
|
||||
// so instead initialize with a shared view.
|
||||
private static final DrawView EMPTY_DRAW_VIEW = new DrawView(0);
|
||||
|
||||
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
|
||||
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
|
||||
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
|
||||
private FlatShadowNode[] mNativeChildren = FlatShadowNode.EMPTY_ARRAY;
|
||||
private NodeRegion mNodeRegion = NodeRegion.EMPTY;
|
||||
private int mNativeParentTag;
|
||||
private int mViewLeft;
|
||||
private int mViewTop;
|
||||
private int mViewRight;
|
||||
private int mViewBottom;
|
||||
private boolean mBackingViewIsCreated;
|
||||
private @Nullable DrawView mDrawView;
|
||||
private @Nullable DrawBackgroundColor mDrawBackground;
|
||||
private boolean mIsUpdated = true;
|
||||
private boolean mForceMountChildrenToView;
|
||||
private float mClipLeft;
|
||||
private float mClipTop;
|
||||
private float mClipRight;
|
||||
private float mClipBottom;
|
||||
|
||||
// Used to track whether any of the NodeRegions overflow this Node. This is used to determine
|
||||
// whether or not we can detach this Node in the context of a container with
|
||||
// setRemoveClippedSubviews enabled.
|
||||
private boolean mOverflowsContainer;
|
||||
// this Rect contains the offset to get the "logical bounds" (i.e. bounds that include taking
|
||||
// into account overflow visible).
|
||||
private Rect mLogicalOffset = LOGICAL_OFFSET_EMPTY;
|
||||
|
||||
// last OnLayoutEvent info, only used when shouldNotifyOnLayout() is true.
|
||||
private int mLayoutX;
|
||||
private int mLayoutY;
|
||||
private int mLayoutWidth;
|
||||
private int mLayoutHeight;
|
||||
|
||||
// clip radius
|
||||
float mClipRadius;
|
||||
boolean mClipToBounds = false;
|
||||
|
||||
/* package */ void handleUpdateProperties(ReactStylesDiffMap styles) {
|
||||
if (!mountsToView()) {
|
||||
// Make sure we mount this FlatShadowNode to a View if any of these properties are present.
|
||||
if (styles.hasKey(PROP_OPACITY) ||
|
||||
styles.hasKey(PROP_RENDER_TO_HARDWARE_TEXTURE) ||
|
||||
styles.hasKey(PROP_TEST_ID) ||
|
||||
styles.hasKey(PROP_ACCESSIBILITY_LABEL) ||
|
||||
styles.hasKey(PROP_ACCESSIBILITY_COMPONENT_TYPE) ||
|
||||
styles.hasKey(PROP_ACCESSIBILITY_LIVE_REGION) ||
|
||||
styles.hasKey(PROP_TRANSFORM) ||
|
||||
styles.hasKey(PROP_IMPORTANT_FOR_ACCESSIBILITY) ||
|
||||
styles.hasKey(PROP_REMOVE_CLIPPED_SUBVIEWS)) {
|
||||
forceMountToView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ final void forceMountChildrenToView() {
|
||||
if (mForceMountChildrenToView) {
|
||||
return;
|
||||
}
|
||||
|
||||
mForceMountChildrenToView = true;
|
||||
for (int i = 0, childCount = getChildCount(); i != childCount; ++i) {
|
||||
ReactShadowNode child = getChildAt(i);
|
||||
if (child instanceof FlatShadowNode) {
|
||||
((FlatShadowNode) child).forceMountToView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects DrawCommands produced by this FlatShadowNode.
|
||||
*/
|
||||
protected void collectState(
|
||||
StateBuilder stateBuilder,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
if (mDrawBackground != null) {
|
||||
mDrawBackground = (DrawBackgroundColor) mDrawBackground.updateBoundsAndFreeze(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
stateBuilder.addDrawCommand(mDrawBackground);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not this node draws anything
|
||||
*
|
||||
* This is used to decide whether or not to collect the NodeRegion for this node. This ensures
|
||||
* that any FlatShadowNode that does not emit any DrawCommands should not bother handling touch
|
||||
* (i.e. if it draws absolutely nothing, it is, for all intents and purposes, a layout only node).
|
||||
*
|
||||
* @return whether or not this is node draws anything
|
||||
*/
|
||||
boolean doesDraw() {
|
||||
// if it mounts to view or draws a background, we can collect it - otherwise, no, unless a
|
||||
// child suggests some alternative behavior
|
||||
return mDrawView != null || mDrawBackground != null;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.BACKGROUND_COLOR)
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
mDrawBackground = (backgroundColor == 0) ? null : new DrawBackgroundColor(backgroundColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverflow(String overflow) {
|
||||
super.setOverflow(overflow);
|
||||
mClipToBounds = "hidden".equals(overflow);
|
||||
if (mClipToBounds) {
|
||||
mOverflowsContainer = false;
|
||||
if (mClipRadius > DrawView.MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
// mount to a view if we are overflow: hidden and are clipping, so that we can do one
|
||||
// clipPath to clip all the children of this node (both DrawCommands and Views).
|
||||
forceMountToView();
|
||||
}
|
||||
} else {
|
||||
updateOverflowsContainer();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public final boolean clipToBounds() {
|
||||
return mClipToBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getScreenX() {
|
||||
return mViewLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getScreenY() {
|
||||
return mViewTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getScreenWidth() {
|
||||
if (mountsToView()) {
|
||||
return mViewRight - mViewLeft;
|
||||
} else {
|
||||
return Math.round(mNodeRegion.getRight() - mNodeRegion.getLeft());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getScreenHeight() {
|
||||
if (mountsToView()) {
|
||||
return mViewBottom - mViewTop;
|
||||
} else {
|
||||
return Math.round(mNodeRegion.getBottom() - mNodeRegion.getTop());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChildAt(ReactShadowNodeImpl child, int i) {
|
||||
super.addChildAt(child, i);
|
||||
if (mForceMountChildrenToView && child instanceof FlatShadowNode) {
|
||||
((FlatShadowNode) child).forceMountToView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
FlatShadowNode node = this;
|
||||
|
||||
while (true) {
|
||||
if (node.mountsToView()) {
|
||||
if (node.mIsUpdated) {
|
||||
// already updated
|
||||
return;
|
||||
}
|
||||
|
||||
node.mIsUpdated = true;
|
||||
}
|
||||
|
||||
ReactShadowNode parent = node.getParent();
|
||||
if (parent == null) {
|
||||
// not attached to a hierarchy yet
|
||||
return;
|
||||
}
|
||||
|
||||
node = (FlatShadowNode) parent;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markUpdated() {
|
||||
super.markUpdated();
|
||||
mIsUpdated = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/* package */ final boolean isUpdated() {
|
||||
return mIsUpdated;
|
||||
}
|
||||
|
||||
/* package */ final void resetUpdated() {
|
||||
mIsUpdated = false;
|
||||
}
|
||||
|
||||
/* package */ final boolean clipBoundsChanged(
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
return mClipLeft != clipLeft || mClipTop != clipTop ||
|
||||
mClipRight != clipRight || mClipBottom != clipBottom;
|
||||
}
|
||||
|
||||
/* package */ final void setClipBounds(
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
mClipLeft = clipLeft;
|
||||
mClipTop = clipTop;
|
||||
mClipRight = clipRight;
|
||||
mClipBottom = clipBottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of DrawCommands to perform during the View's draw pass.
|
||||
*/
|
||||
/* package */ final DrawCommand[] getDrawCommands() {
|
||||
return mDrawCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an array of DrawCommands to perform during the View's draw pass. StateBuilder uses old
|
||||
* draw commands to compare to new draw commands and see if the View needs to be redrawn.
|
||||
*/
|
||||
/* package */ final void setDrawCommands(DrawCommand[] drawCommands) {
|
||||
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 */ final void setAttachDetachListeners(AttachDetachListener[] listeners) {
|
||||
mAttachDetachListeners = listeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of AttachDetachListeners associated with this shadow node.
|
||||
*/
|
||||
/* package */ final AttachDetachListener[] getAttachDetachListeners() {
|
||||
return mAttachDetachListeners;
|
||||
}
|
||||
|
||||
/* package */ final FlatShadowNode[] getNativeChildren() {
|
||||
return mNativeChildren;
|
||||
}
|
||||
|
||||
/* package */ final void setNativeChildren(FlatShadowNode[] nativeChildren) {
|
||||
mNativeChildren = nativeChildren;
|
||||
}
|
||||
|
||||
/* package */ final int getNativeParentTag() {
|
||||
return mNativeParentTag;
|
||||
}
|
||||
|
||||
/* package */ final void setNativeParentTag(int nativeParentTag) {
|
||||
mNativeParentTag = nativeParentTag;
|
||||
}
|
||||
|
||||
/* package */ final NodeRegion[] getNodeRegions() {
|
||||
return mNodeRegions;
|
||||
}
|
||||
|
||||
/* package */ final void setNodeRegions(NodeRegion[] nodeRegion) {
|
||||
mNodeRegions = nodeRegion;
|
||||
updateOverflowsContainer();
|
||||
}
|
||||
|
||||
/* package */ final void updateOverflowsContainer() {
|
||||
boolean overflowsContainer = false;
|
||||
int width = (int) (mNodeRegion.getRight() - mNodeRegion.getLeft());
|
||||
int height = (int) (mNodeRegion.getBottom() - mNodeRegion.getTop());
|
||||
|
||||
float leftBound = 0;
|
||||
float rightBound = width;
|
||||
float topBound = 0;
|
||||
float bottomBound = height;
|
||||
Rect logicalOffset = null;
|
||||
|
||||
// when we are overflow:visible, we try to figure out if any of the children are outside
|
||||
// of the bounds of this view. since NodeRegion bounds are relative to their parent (i.e.
|
||||
// 0, 0 is always the start), we see how much outside of the bounds we are (negative left
|
||||
// or top, or bottom that's more than height or right that's more than width). we set these
|
||||
// offsets in mLogicalOffset for being able to more intelligently determine whether or not
|
||||
// to clip certain subviews.
|
||||
if (!mClipToBounds && height > 0 && width > 0) {
|
||||
for (NodeRegion region : mNodeRegions) {
|
||||
if (region.getLeft() < leftBound) {
|
||||
leftBound = region.getLeft();
|
||||
overflowsContainer = true;
|
||||
}
|
||||
|
||||
if (region.getRight() > rightBound) {
|
||||
rightBound = region.getRight();
|
||||
overflowsContainer = true;
|
||||
}
|
||||
|
||||
if (region.getTop() < topBound) {
|
||||
topBound = region.getTop();
|
||||
overflowsContainer = true;
|
||||
}
|
||||
|
||||
if (region.getBottom() > bottomBound) {
|
||||
bottomBound = region.getBottom();
|
||||
overflowsContainer = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (overflowsContainer) {
|
||||
logicalOffset = new Rect(
|
||||
(int) leftBound,
|
||||
(int) topBound,
|
||||
(int) (rightBound - width),
|
||||
(int) (bottomBound - height));
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't overflow, let's check if any of the immediate children overflow.
|
||||
// this is "indirectly recursive," since this method is called when setNodeRegions is called,
|
||||
// and the children call setNodeRegions before their parent. consequently, when a node deep
|
||||
// inside the tree overflows, its immediate parent has mOverflowsContainer set to true, and,
|
||||
// by extension, so do all of its ancestors, sufficing here to only check the immediate
|
||||
// child's mOverflowsContainer value instead of recursively asking if each child overflows its
|
||||
// container.
|
||||
if (!overflowsContainer && mNodeRegion != NodeRegion.EMPTY) {
|
||||
int children = getChildCount();
|
||||
for (int i = 0; i < children; i++) {
|
||||
ReactShadowNode node = getChildAt(i);
|
||||
if (node instanceof FlatShadowNode && ((FlatShadowNode) node).mOverflowsContainer) {
|
||||
Rect childLogicalOffset = ((FlatShadowNode) node).mLogicalOffset;
|
||||
if (logicalOffset == null) {
|
||||
logicalOffset = new Rect();
|
||||
}
|
||||
// TODO: t11674025 - improve this - a grandparent may end up having smaller logical
|
||||
// bounds than its children (because the grandparent's size may be larger than that of
|
||||
// its child, so the grandchild overflows its parent but not its grandparent). currently,
|
||||
// if a 100x100 view has a 5x5 view, and inside it has a 10x10 view, the inner most view
|
||||
// overflows its parent but not its grandparent - the logical bounds on the grandparent
|
||||
// will still be 5x5 (because they're inherited from the child's logical bounds). this
|
||||
// has the effect of causing us to clip 5px later than we really have to.
|
||||
logicalOffset.union(childLogicalOffset);
|
||||
overflowsContainer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if things changed, notify the parent(s) about said changes - while in many cases, this will
|
||||
// be extra work (since we process this for the parents after the children), in some cases,
|
||||
// we may have no new node regions in the parent, but have a new node region in the child, and,
|
||||
// as a result, the parent may not get the correct value for overflows container.
|
||||
if (mOverflowsContainer != overflowsContainer) {
|
||||
mOverflowsContainer = overflowsContainer;
|
||||
mLogicalOffset = logicalOffset == null ? LOGICAL_OFFSET_EMPTY : logicalOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void updateNodeRegion(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
boolean isVirtual) {
|
||||
if (!mNodeRegion.matches(left, top, right, bottom, isVirtual)) {
|
||||
setNodeRegion(new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void setNodeRegion(NodeRegion nodeRegion) {
|
||||
mNodeRegion = nodeRegion;
|
||||
updateOverflowsContainer();
|
||||
}
|
||||
|
||||
/* package */ final NodeRegion getNodeRegion() {
|
||||
return mNodeRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets boundaries of the View that this node maps to relative to the parent left/top coordinate.
|
||||
*/
|
||||
/* package */ final void setViewBounds(int left, int top, int right, int bottom) {
|
||||
mViewLeft = left;
|
||||
mViewTop = top;
|
||||
mViewRight = right;
|
||||
mViewBottom = bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Left position of the View this node maps to relative to the parent View.
|
||||
*/
|
||||
/* package */ final int getViewLeft() {
|
||||
return mViewLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top position of the View this node maps to relative to the parent View.
|
||||
*/
|
||||
/* package */ final int getViewTop() {
|
||||
return mViewTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right position of the View this node maps to relative to the parent View.
|
||||
*/
|
||||
/* package */ final int getViewRight() {
|
||||
return mViewRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom position of the View this node maps to relative to the parent View.
|
||||
*/
|
||||
/* package */ final int getViewBottom() {
|
||||
return mViewBottom;
|
||||
}
|
||||
|
||||
/* package */ final void forceMountToView() {
|
||||
if (isVirtual()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDrawView == null) {
|
||||
// Create a new DrawView, but we might not know our react tag yet, so set it to 0 in the
|
||||
// meantime.
|
||||
mDrawView = EMPTY_DRAW_VIEW;
|
||||
invalidate();
|
||||
|
||||
// reset NodeRegion to allow it getting garbage-collected
|
||||
mNodeRegion = NodeRegion.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ final DrawView collectDrawView(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
Assertions.assumeNotNull(mDrawView);
|
||||
if (mDrawView == EMPTY_DRAW_VIEW) {
|
||||
// This is the first time we have collected this DrawView, but we have to create a new
|
||||
// DrawView anyway, as reactTag is final, and our DrawView instance is the static copy.
|
||||
mDrawView = new DrawView(getReactTag());
|
||||
}
|
||||
|
||||
// avoid path clipping if overflow: visible
|
||||
float clipRadius = mClipToBounds ? mClipRadius : 0.0f;
|
||||
// We have the correct react tag, but we may need a new copy with updated bounds. If the bounds
|
||||
// match or were never set, the same view is returned.
|
||||
mDrawView = mDrawView.collectDrawView(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left + mLogicalOffset.left,
|
||||
top + mLogicalOffset.top,
|
||||
right + mLogicalOffset.right,
|
||||
bottom + mLogicalOffset.bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom,
|
||||
clipRadius);
|
||||
return mDrawView;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
/* package */ final OnLayoutEvent obtainLayoutEvent(int x, int y, int width, int height) {
|
||||
if (mLayoutX == x && mLayoutY == y && mLayoutWidth == width && mLayoutHeight == height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mLayoutX = x;
|
||||
mLayoutY = y;
|
||||
mLayoutWidth = width;
|
||||
mLayoutHeight = height;
|
||||
|
||||
return OnLayoutEvent.obtain(getReactTag(), x, y, width, height);
|
||||
}
|
||||
|
||||
/* package */ final boolean mountsToView() {
|
||||
return mDrawView != null;
|
||||
}
|
||||
|
||||
/* package */ final boolean isBackingViewCreated() {
|
||||
return mBackingViewIsCreated;
|
||||
}
|
||||
|
||||
/* package */ final void signalBackingViewIsCreated() {
|
||||
mBackingViewIsCreated = true;
|
||||
}
|
||||
|
||||
public boolean clipsSubviews() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isHorizontal() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
|
||||
/**
|
||||
* Base class for RCTVirtualText and RCTRawText.
|
||||
*/
|
||||
/* package */ abstract class FlatTextShadowNode extends FlatShadowNode {
|
||||
|
||||
// these 2 are only used between collectText() and applySpans() calls.
|
||||
private int mTextBegin;
|
||||
private int mTextEnd;
|
||||
|
||||
/**
|
||||
* Propagates changes up to RCTText without dirtying current node.
|
||||
*/
|
||||
protected void notifyChanged(boolean shouldRemeasure) {
|
||||
ReactShadowNode parent = getParent();
|
||||
if (parent instanceof FlatTextShadowNode) {
|
||||
((FlatTextShadowNode) parent).notifyChanged(shouldRemeasure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively visits FlatTextShadowNode and its children,
|
||||
* appending text to SpannableStringBuilder.
|
||||
*/
|
||||
/* package */ final void collectText(SpannableStringBuilder builder) {
|
||||
mTextBegin = builder.length();
|
||||
performCollectText(builder);
|
||||
mTextEnd = builder.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to allow empty spans to be set
|
||||
* This is used to bypass an optimization in {@code applySpans} that skips applying spans if
|
||||
* there is no text (since, for TextInput, for example, we want to apply the span even if there
|
||||
* is no text so that newly typed text gets styled properly).
|
||||
*
|
||||
* @return a boolean representing whether or not we should allow empty spans
|
||||
*/
|
||||
/* package */ boolean shouldAllowEmptySpans() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* package */ boolean isEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively visits FlatTextShadowNode and its children,
|
||||
* applying spans to SpannableStringBuilder.
|
||||
*/
|
||||
/* package */ final void applySpans(SpannableStringBuilder builder, boolean isEditable) {
|
||||
if (mTextBegin != mTextEnd || shouldAllowEmptySpans()) {
|
||||
performApplySpans(builder, mTextBegin, mTextEnd, isEditable);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void performCollectText(SpannableStringBuilder builder);
|
||||
protected abstract void performApplySpans(
|
||||
SpannableStringBuilder builder,
|
||||
int begin,
|
||||
int end,
|
||||
boolean isEditable);
|
||||
protected abstract void performCollectAttachDetachListeners(StateBuilder stateBuilder);
|
||||
}
|
|
@ -1,608 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.modules.fresco.FrescoModule;
|
||||
import com.facebook.react.modules.i18nmanager.I18nUtil;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
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.yoga.YogaDirection;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* FlatUIImplementation builds on top of UIImplementation and allows pre-creating everything
|
||||
* required for drawing (DrawCommands) and touching (NodeRegions) views in background thread
|
||||
* for faster drawing and interactions.
|
||||
*/
|
||||
public class FlatUIImplementation extends UIImplementation {
|
||||
|
||||
private static final Map<String, Class<? extends ViewManager>> flatManagerClassMap;
|
||||
|
||||
static {
|
||||
flatManagerClassMap = new HashMap<>();
|
||||
flatManagerClassMap.put(RCTViewManager.REACT_CLASS, RCTViewManager.class);
|
||||
flatManagerClassMap.put(RCTTextManager.REACT_CLASS, RCTTextManager.class);
|
||||
flatManagerClassMap.put(RCTRawTextManager.REACT_CLASS, RCTRawTextManager.class);
|
||||
flatManagerClassMap.put(RCTVirtualTextManager.REACT_CLASS, RCTVirtualTextManager.class);
|
||||
flatManagerClassMap.put(RCTTextInlineImageManager.REACT_CLASS, RCTTextInlineImageManager.class);
|
||||
flatManagerClassMap.put(RCTImageViewManager.REACT_CLASS, RCTImageViewManager.class);
|
||||
flatManagerClassMap.put(RCTTextInputManager.REACT_CLASS, RCTTextInputManager.class);
|
||||
flatManagerClassMap.put(RCTViewPagerManager.REACT_CLASS, RCTViewPagerManager.class);
|
||||
flatManagerClassMap.put(FlatARTSurfaceViewManager.REACT_CLASS, FlatARTSurfaceViewManager.class);
|
||||
flatManagerClassMap.put(RCTModalHostManager.REACT_CLASS, RCTModalHostManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the map of view managers, checking that the managers FlatUI requires are correctly
|
||||
* overridden.
|
||||
*/
|
||||
private static Map<String, ViewManager> buildViewManagerMap(List<ViewManager> viewManagers) {
|
||||
Map<String, ViewManager> viewManagerMap = new HashMap<>();
|
||||
for (ViewManager viewManager : viewManagers) {
|
||||
viewManagerMap.put(viewManager.getName(), viewManager);
|
||||
}
|
||||
for (Map.Entry<String, Class<? extends ViewManager>> entry : flatManagerClassMap.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
ViewManager maybeFlatViewManager = viewManagerMap.get(name);
|
||||
if (maybeFlatViewManager == null) {
|
||||
// We don't have a view manager for this name in the package, no need to add one.
|
||||
continue;
|
||||
}
|
||||
|
||||
Class<? extends ViewManager> flatClazz = entry.getValue();
|
||||
if (maybeFlatViewManager.getClass() != flatClazz) {
|
||||
// If we have instances that have flat equivalents, override them.
|
||||
try {
|
||||
viewManagerMap.put(name, flatClazz.newInstance());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to access flat class for " + name, e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Unable to instantiate flat class for " + name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return viewManagerMap;
|
||||
}
|
||||
|
||||
public static FlatUIImplementation createInstance(
|
||||
ReactApplicationContext reactContext,
|
||||
List<ViewManager> viewManagers,
|
||||
EventDispatcher eventDispatcher,
|
||||
boolean memoryImprovementEnabled,
|
||||
int minTimeLeftInFrameForNonBatchedOperationMs) {
|
||||
|
||||
Map<String, ViewManager> viewManagerMap = buildViewManagerMap(viewManagers);
|
||||
|
||||
RCTImageViewManager imageViewManager =
|
||||
(RCTImageViewManager) viewManagerMap.get(RCTImageViewManager.REACT_CLASS);
|
||||
if (imageViewManager != null) {
|
||||
Object callerContext = imageViewManager.getCallerContext();
|
||||
if (callerContext != null) {
|
||||
RCTImageView.setCallerContext(callerContext);
|
||||
}
|
||||
}
|
||||
DraweeRequestHelper.setResources(reactContext.getResources());
|
||||
|
||||
TypefaceCache.setAssetManager(reactContext.getAssets());
|
||||
|
||||
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagerMap);
|
||||
FlatNativeViewHierarchyManager nativeViewHierarchyManager = new FlatNativeViewHierarchyManager(
|
||||
viewManagerRegistry);
|
||||
FlatUIViewOperationQueue operationsQueue =
|
||||
new FlatUIViewOperationQueue(
|
||||
reactContext, nativeViewHierarchyManager, minTimeLeftInFrameForNonBatchedOperationMs);
|
||||
return new FlatUIImplementation(
|
||||
reactContext,
|
||||
imageViewManager,
|
||||
viewManagerRegistry,
|
||||
operationsQueue,
|
||||
eventDispatcher,
|
||||
memoryImprovementEnabled
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that sorts moveTo/moveFrom arrays passed to #manageChildren().
|
||||
* Not used outside of the said method.
|
||||
*/
|
||||
private final MoveProxy mMoveProxy = new MoveProxy();
|
||||
private final ReactApplicationContext mReactContext;
|
||||
private @Nullable RCTImageViewManager mRCTImageViewManager;
|
||||
private final StateBuilder mStateBuilder;
|
||||
private final boolean mMemoryImprovementEnabled;
|
||||
|
||||
private FlatUIImplementation(
|
||||
ReactApplicationContext reactContext,
|
||||
@Nullable RCTImageViewManager rctImageViewManager,
|
||||
ViewManagerRegistry viewManagers,
|
||||
FlatUIViewOperationQueue operationsQueue,
|
||||
EventDispatcher eventDispatcher,
|
||||
boolean memoryImprovementEnabled) {
|
||||
super(reactContext, viewManagers, operationsQueue, eventDispatcher);
|
||||
mReactContext = reactContext;
|
||||
mRCTImageViewManager = rctImageViewManager;
|
||||
mStateBuilder = new StateBuilder(operationsQueue);
|
||||
mMemoryImprovementEnabled = memoryImprovementEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactShadowNode createRootShadowNode() {
|
||||
if (mRCTImageViewManager != null) {
|
||||
// This is not the best place to initialize DraweeRequestHelper, but order of module
|
||||
// initialization is undefined, and this is pretty much the earliest when we are guarantied
|
||||
// that Fresco is initialized and DraweeControllerBuilder can be queried. This also happens
|
||||
// relatively rarely to have any performance considerations.
|
||||
mReactContext.getNativeModule(FrescoModule.class); // initialize Fresco
|
||||
DraweeRequestHelper.setDraweeControllerBuilder(
|
||||
mRCTImageViewManager.getDraweeControllerBuilder());
|
||||
mRCTImageViewManager = null;
|
||||
}
|
||||
|
||||
ReactShadowNode node = new FlatRootShadowNode();
|
||||
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
|
||||
if (sharedI18nUtilInstance.isRTL(mReactContext)) {
|
||||
node.setLayoutDirection(YogaDirection.RTL);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactShadowNode createShadowNode(String className) {
|
||||
ReactShadowNode cssNode = super.createShadowNode(className);
|
||||
if (cssNode instanceof FlatShadowNode || cssNode.isVirtual()) {
|
||||
return cssNode;
|
||||
}
|
||||
|
||||
ViewManager viewManager = resolveViewManager(className);
|
||||
return new NativeViewWrapper(viewManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCreateView(
|
||||
ReactShadowNode cssNode,
|
||||
int rootViewTag,
|
||||
@Nullable ReactStylesDiffMap styles) {
|
||||
if (cssNode instanceof FlatShadowNode) {
|
||||
FlatShadowNode node = (FlatShadowNode) cssNode;
|
||||
|
||||
if (styles != null) {
|
||||
node.handleUpdateProperties(styles);
|
||||
}
|
||||
|
||||
if (node.mountsToView()) {
|
||||
mStateBuilder.enqueueCreateOrUpdateView(node, styles);
|
||||
}
|
||||
} else {
|
||||
super.handleCreateView(cssNode, rootViewTag, styles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleUpdateView(
|
||||
ReactShadowNode cssNode,
|
||||
String className,
|
||||
ReactStylesDiffMap styles) {
|
||||
if (cssNode instanceof FlatShadowNode) {
|
||||
FlatShadowNode node = (FlatShadowNode) cssNode;
|
||||
|
||||
node.handleUpdateProperties(styles);
|
||||
|
||||
if (node.mountsToView()) {
|
||||
mStateBuilder.enqueueCreateOrUpdateView(node, styles);
|
||||
}
|
||||
} else {
|
||||
super.handleUpdateView(cssNode, className, styles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manageChildren(
|
||||
int viewTag,
|
||||
@Nullable ReadableArray moveFrom,
|
||||
@Nullable ReadableArray moveTo,
|
||||
@Nullable ReadableArray addChildTags,
|
||||
@Nullable ReadableArray addAtIndices,
|
||||
@Nullable ReadableArray removeFrom) {
|
||||
|
||||
ReactShadowNode parentNode = resolveShadowNode(viewTag);
|
||||
|
||||
// moveFrom and removeFrom are defined in original order before any mutations.
|
||||
removeChildren(parentNode, moveFrom, moveTo, removeFrom);
|
||||
|
||||
// moveTo and addAtIndices are defined in final order after all the mutations applied.
|
||||
addChildren(parentNode, addChildTags, addAtIndices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChildren(
|
||||
int viewTag,
|
||||
ReadableArray children) {
|
||||
|
||||
ReactShadowNode parentNode = resolveShadowNode(viewTag);
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
ReactShadowNode addToChild = resolveShadowNode(children.getInt(i));
|
||||
addChildAt(parentNode, addToChild, i, i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void measure(int reactTag, Callback callback) {
|
||||
measureHelper(reactTag, false, callback);
|
||||
}
|
||||
|
||||
private void measureHelper(int reactTag, boolean relativeToWindow, Callback callback) {
|
||||
FlatShadowNode node = (FlatShadowNode) resolveShadowNode(reactTag);
|
||||
if (node.mountsToView()) {
|
||||
mStateBuilder.ensureBackingViewIsCreated(node);
|
||||
if (relativeToWindow) {
|
||||
super.measureInWindow(reactTag, callback);
|
||||
} else {
|
||||
super.measure(reactTag, callback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// virtual nodes do not have values for width and height, so get these values
|
||||
// from the first non-virtual parent node
|
||||
while (node != null && node.isVirtual()) {
|
||||
node = (FlatShadowNode) node.getParent();
|
||||
}
|
||||
|
||||
if (node == null) {
|
||||
// everything is virtual, this shouldn't happen so just silently return
|
||||
return;
|
||||
}
|
||||
|
||||
float width = node.getLayoutWidth();
|
||||
float height = node.getLayoutHeight();
|
||||
|
||||
boolean nodeMountsToView = node.mountsToView();
|
||||
// this is to avoid double-counting xInParent and yInParent when we visit
|
||||
// the while loop, below.
|
||||
float xInParent = nodeMountsToView ? node.getLayoutX() : 0;
|
||||
float yInParent = nodeMountsToView ? node.getLayoutY() : 0;
|
||||
|
||||
while (!node.mountsToView()) {
|
||||
if (!node.isVirtual()) {
|
||||
xInParent += node.getLayoutX();
|
||||
yInParent += node.getLayoutY();
|
||||
}
|
||||
|
||||
node = Assertions.assumeNotNull((FlatShadowNode) node.getParent());
|
||||
}
|
||||
|
||||
float parentWidth = node.getLayoutWidth();
|
||||
float parentHeight = node.getLayoutHeight();
|
||||
|
||||
FlatUIViewOperationQueue operationsQueue = mStateBuilder.getOperationsQueue();
|
||||
operationsQueue.enqueueMeasureVirtualView(
|
||||
node.getReactTag(),
|
||||
xInParent / parentWidth,
|
||||
yInParent / parentHeight,
|
||||
width / parentWidth,
|
||||
height / parentHeight,
|
||||
relativeToWindow,
|
||||
callback);
|
||||
}
|
||||
|
||||
private void ensureMountsToViewAndBackingViewIsCreated(int reactTag) {
|
||||
FlatShadowNode node = (FlatShadowNode) resolveShadowNode(reactTag);
|
||||
if (node.isBackingViewCreated()) {
|
||||
return;
|
||||
}
|
||||
node.forceMountToView();
|
||||
mStateBuilder.ensureBackingViewIsCreated(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) {
|
||||
ensureMountsToViewAndBackingViewIsCreated(reactTag);
|
||||
super.findSubviewIn(reactTag, targetX, targetY, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void measureInWindow(int reactTag, Callback callback) {
|
||||
measureHelper(reactTag, true, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAnimation(int reactTag, int animationID, Callback onSuccess) {
|
||||
ensureMountsToViewAndBackingViewIsCreated(reactTag);
|
||||
super.addAnimation(reactTag, animationID, onSuccess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) {
|
||||
// Make sure that our target view is actually a view, then delay command dispatch until after
|
||||
// we have updated the view hierarchy.
|
||||
ensureMountsToViewAndBackingViewIsCreated(reactTag);
|
||||
mStateBuilder.enqueueViewManagerCommand(reactTag, commandId, commandArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
|
||||
ensureMountsToViewAndBackingViewIsCreated(reactTag);
|
||||
super.showPopupMenu(reactTag, items, error, success);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAccessibilityEvent(int reactTag, int eventType) {
|
||||
ensureMountsToViewAndBackingViewIsCreated(reactTag);
|
||||
super.sendAccessibilityEvent(reactTag, eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all children defined by moveFrom and removeFrom from a given parent,
|
||||
* preparing elements in moveFrom to be re-added at proper index.
|
||||
*/
|
||||
private void removeChildren(
|
||||
ReactShadowNode parentNode,
|
||||
@Nullable ReadableArray moveFrom,
|
||||
@Nullable ReadableArray moveTo,
|
||||
@Nullable ReadableArray removeFrom) {
|
||||
|
||||
int prevIndex = Integer.MAX_VALUE;
|
||||
|
||||
mMoveProxy.setup(moveFrom, moveTo);
|
||||
|
||||
int moveFromIndex = mMoveProxy.size() - 1;
|
||||
int moveFromChildIndex = (moveFromIndex == -1) ? -1 : mMoveProxy.getMoveFrom(moveFromIndex);
|
||||
|
||||
int numToRemove = removeFrom == null ? 0 : removeFrom.size();
|
||||
int[] indicesToRemove = new int[numToRemove];
|
||||
if (numToRemove > 0) {
|
||||
Assertions.assertNotNull(removeFrom);
|
||||
for (int i = 0; i < numToRemove; i++) {
|
||||
int indexToRemove = removeFrom.getInt(i);
|
||||
indicesToRemove[i] = indexToRemove;
|
||||
}
|
||||
}
|
||||
|
||||
// this isn't guaranteed to be sorted actually
|
||||
Arrays.sort(indicesToRemove);
|
||||
|
||||
int removeFromIndex;
|
||||
int removeFromChildIndex;
|
||||
if (removeFrom == null) {
|
||||
removeFromIndex = -1;
|
||||
removeFromChildIndex = -1;
|
||||
} else {
|
||||
removeFromIndex = indicesToRemove.length - 1;
|
||||
removeFromChildIndex = indicesToRemove[removeFromIndex];
|
||||
}
|
||||
|
||||
// both moveFrom and removeFrom are already sorted, but combined order is not sorted. Use
|
||||
// a merge step from mergesort to walk over both arrays and extract elements in sorted order.
|
||||
|
||||
while (true) {
|
||||
if (moveFromChildIndex > removeFromChildIndex) {
|
||||
moveChild(removeChildAt(parentNode, moveFromChildIndex, prevIndex), moveFromIndex);
|
||||
prevIndex = moveFromChildIndex;
|
||||
|
||||
--moveFromIndex;
|
||||
moveFromChildIndex = (moveFromIndex == -1) ? -1 : mMoveProxy.getMoveFrom(moveFromIndex);
|
||||
} else if (removeFromChildIndex > moveFromChildIndex) {
|
||||
removeChild(removeChildAt(parentNode, removeFromChildIndex, prevIndex), parentNode);
|
||||
prevIndex = removeFromChildIndex;
|
||||
|
||||
--removeFromIndex;
|
||||
removeFromChildIndex = (removeFromIndex == -1) ? -1 : indicesToRemove[removeFromIndex];
|
||||
} else {
|
||||
// moveFromChildIndex == removeFromChildIndex can only be if both are equal to -1
|
||||
// which means that we exhausted both arrays, and all children are removed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters given element and all of its children from ShadowNodeRegistry,
|
||||
* and drops all Views used by it and its children.
|
||||
*/
|
||||
private void removeChild(ReactShadowNode child, ReactShadowNode parentNode) {
|
||||
dropNativeViews(child, parentNode);
|
||||
removeShadowNode(child);
|
||||
}
|
||||
|
||||
private void dropNativeViews(ReactShadowNode child, ReactShadowNode parentNode) {
|
||||
if (child instanceof FlatShadowNode) {
|
||||
FlatShadowNode node = (FlatShadowNode) child;
|
||||
if (node.mountsToView() && node.isBackingViewCreated()) {
|
||||
int tag = -1;
|
||||
|
||||
// this tag is used to remove the reference to this dropping view if it it's clipped.
|
||||
// we need to figure out the correct "view parent" tag to do this. note that this is
|
||||
// not necessarily getParent().getReactTag(), since getParent() may represent something
|
||||
// that's not a View - we need to find the first View (what would represent
|
||||
// view.getParent() on the ui thread), which is what this code is finding.
|
||||
ReactShadowNode tmpNode = parentNode;
|
||||
while (tmpNode != null) {
|
||||
if (tmpNode instanceof FlatShadowNode) {
|
||||
FlatShadowNode flatTmpNode = (FlatShadowNode) tmpNode;
|
||||
if (flatTmpNode.mountsToView() && flatTmpNode.isBackingViewCreated() &&
|
||||
flatTmpNode.getParent() != null) {
|
||||
tag = flatTmpNode.getReactTag();
|
||||
break;
|
||||
}
|
||||
}
|
||||
tmpNode = tmpNode.getParent();
|
||||
}
|
||||
|
||||
// this will recursively drop all subviews
|
||||
mStateBuilder.dropView(node, tag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, childCount = child.getChildCount(); i != childCount; ++i) {
|
||||
dropNativeViews(child.getChildAt(i), child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a given element to be moved to a new position.
|
||||
*/
|
||||
private void moveChild(ReactShadowNode child, int moveFromIndex) {
|
||||
mMoveProxy.setChildMoveFrom(moveFromIndex, child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all children from addChildTags and moveFrom/moveTo.
|
||||
*/
|
||||
private void addChildren(
|
||||
ReactShadowNode parentNode,
|
||||
@Nullable ReadableArray addChildTags,
|
||||
@Nullable ReadableArray addAtIndices) {
|
||||
|
||||
int prevIndex = -1;
|
||||
|
||||
int moveToIndex;
|
||||
int moveToChildIndex;
|
||||
if (mMoveProxy.size() == 0) {
|
||||
moveToIndex = Integer.MAX_VALUE;
|
||||
moveToChildIndex = Integer.MAX_VALUE;
|
||||
} else {
|
||||
moveToIndex = 0;
|
||||
moveToChildIndex = mMoveProxy.getMoveTo(0);
|
||||
}
|
||||
|
||||
int numNodesToAdd;
|
||||
int addToIndex;
|
||||
int addToChildIndex;
|
||||
if (addAtIndices == null) {
|
||||
numNodesToAdd = 0;
|
||||
addToIndex = Integer.MAX_VALUE;
|
||||
addToChildIndex = Integer.MAX_VALUE;
|
||||
} else {
|
||||
numNodesToAdd = addAtIndices.size();
|
||||
addToIndex = 0;
|
||||
addToChildIndex = addAtIndices.getInt(0);
|
||||
}
|
||||
|
||||
// both mMoveProxy and addChildTags are already sorted, but combined order is not sorted. Use
|
||||
// a merge step from mergesort to walk over both arrays and extract elements in sorted order.
|
||||
|
||||
while (true) {
|
||||
if (addToChildIndex < moveToChildIndex) {
|
||||
ReactShadowNode addToChild = resolveShadowNode(addChildTags.getInt(addToIndex));
|
||||
addChildAt(parentNode, addToChild, addToChildIndex, prevIndex);
|
||||
prevIndex = addToChildIndex;
|
||||
|
||||
++addToIndex;
|
||||
if (addToIndex == numNodesToAdd) {
|
||||
addToChildIndex = Integer.MAX_VALUE;
|
||||
} else {
|
||||
addToChildIndex = addAtIndices.getInt(addToIndex);
|
||||
}
|
||||
} else if (moveToChildIndex < addToChildIndex) {
|
||||
ReactShadowNode moveToChild = mMoveProxy.getChildMoveTo(moveToIndex);
|
||||
addChildAt(parentNode, moveToChild, moveToChildIndex, prevIndex);
|
||||
prevIndex = moveToChildIndex;
|
||||
|
||||
++moveToIndex;
|
||||
if (moveToIndex == mMoveProxy.size()) {
|
||||
moveToChildIndex = Integer.MAX_VALUE;
|
||||
} else {
|
||||
moveToChildIndex = mMoveProxy.getMoveTo(moveToIndex);
|
||||
}
|
||||
} else {
|
||||
// moveToChildIndex == addToChildIndex can only be if both are equal to Integer.MAX_VALUE
|
||||
// which means that we exhausted both arrays, and all children are added.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a child from parent, verifying that we are removing in descending order.
|
||||
*/
|
||||
private static ReactShadowNode removeChildAt(
|
||||
ReactShadowNode parentNode,
|
||||
int index,
|
||||
int prevIndex) {
|
||||
if (index >= prevIndex) {
|
||||
throw new RuntimeException(
|
||||
"Invariant failure, needs sorting! " + index + " >= " + prevIndex);
|
||||
}
|
||||
|
||||
return parentNode.removeChildAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child to parent, verifying that we are adding in ascending order.
|
||||
*/
|
||||
private static void addChildAt(
|
||||
ReactShadowNode parentNode,
|
||||
ReactShadowNode childNode,
|
||||
int index,
|
||||
int prevIndex) {
|
||||
if (index <= prevIndex) {
|
||||
throw new RuntimeException(
|
||||
"Invariant failure, needs sorting! " + index + " <= " + prevIndex);
|
||||
}
|
||||
|
||||
parentNode.addChildAt(childNode, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateViewHierarchy() {
|
||||
super.updateViewHierarchy();
|
||||
mStateBuilder.afterUpdateViewHierarchy(mEventDispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyUpdatesRecursive(
|
||||
ReactShadowNode cssNode,
|
||||
float absoluteX,
|
||||
float absoluteY) {
|
||||
mStateBuilder.applyUpdates((FlatRootShadowNode) cssNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRootView(int rootViewTag) {
|
||||
if (mMemoryImprovementEnabled) {
|
||||
removeRootShadowNode(rootViewTag);
|
||||
}
|
||||
mStateBuilder.removeRootView(rootViewTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJSResponder(int possiblyVirtualReactTag, boolean blockNativeResponder) {
|
||||
ReactShadowNode node = resolveShadowNode(possiblyVirtualReactTag);
|
||||
while (node.isVirtual()) {
|
||||
node = node.getParent();
|
||||
}
|
||||
int tag = node.getReactTag();
|
||||
|
||||
// if the node in question doesn't mount to a View, find the first parent that does mount to
|
||||
// a View. without this, we'll crash when we try to set the JSResponder, since part of that
|
||||
// is to find the parent view and ask it to not intercept touch events.
|
||||
while (node instanceof FlatShadowNode && !((FlatShadowNode) node).mountsToView()) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
FlatUIViewOperationQueue operationsQueue = mStateBuilder.getOperationsQueue();
|
||||
operationsQueue.enqueueSetJSResponder(
|
||||
node == null ? tag : node.getReactTag(),
|
||||
possiblyVirtualReactTag,
|
||||
blockNativeResponder);
|
||||
}
|
||||
}
|
|
@ -1,527 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.NoSuchNativeViewException;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.TouchTargetHelper;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import java.util.ArrayList;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* FlatUIViewOperationQueue extends {@link UIViewOperationQueue} to add
|
||||
* FlatUIImplementation-specific methods that need to run in UI thread.
|
||||
*/
|
||||
/* package */ final class FlatUIViewOperationQueue extends UIViewOperationQueue {
|
||||
|
||||
private static final int[] MEASURE_BUFFER = new int[4];
|
||||
|
||||
private final FlatNativeViewHierarchyManager mNativeViewHierarchyManager;
|
||||
private final ProcessLayoutRequests mProcessLayoutRequests = new ProcessLayoutRequests();
|
||||
|
||||
private final class ProcessLayoutRequests implements UIViewOperationQueue.UIOperation {
|
||||
@Override
|
||||
public void execute() {
|
||||
FlatViewGroup.processLayoutRequests();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UIOperation that updates DrawCommands for a View defined by reactTag.
|
||||
*/
|
||||
private final class UpdateMountState implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final @Nullable DrawCommand[] mDrawCommands;
|
||||
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
|
||||
private final @Nullable NodeRegion[] mNodeRegions;
|
||||
|
||||
private UpdateMountState(
|
||||
int reactTag,
|
||||
@Nullable DrawCommand[] drawCommands,
|
||||
@Nullable AttachDetachListener[] listeners,
|
||||
@Nullable NodeRegion[] nodeRegions) {
|
||||
mReactTag = reactTag;
|
||||
mDrawCommands = drawCommands;
|
||||
mAttachDetachListeners = listeners;
|
||||
mNodeRegions = nodeRegions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.updateMountState(
|
||||
mReactTag,
|
||||
mDrawCommands,
|
||||
mAttachDetachListeners,
|
||||
mNodeRegions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UIOperation that updates DrawCommands for a View defined by reactTag.
|
||||
*/
|
||||
private final class UpdateClippingMountState implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final @Nullable DrawCommand[] mDrawCommands;
|
||||
private final SparseIntArray mDrawViewIndexMap;
|
||||
private final float[] mCommandMaxBot;
|
||||
private final float[] mCommandMinTop;
|
||||
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
|
||||
private final @Nullable NodeRegion[] mNodeRegions;
|
||||
private final float[] mRegionMaxBot;
|
||||
private final float[] mRegionMinTop;
|
||||
private final boolean mWillMountViews;
|
||||
|
||||
private UpdateClippingMountState(
|
||||
int reactTag,
|
||||
@Nullable DrawCommand[] drawCommands,
|
||||
SparseIntArray drawViewIndexMap,
|
||||
float[] commandMaxBot,
|
||||
float[] commandMinTop,
|
||||
@Nullable AttachDetachListener[] listeners,
|
||||
@Nullable NodeRegion[] nodeRegions,
|
||||
float[] regionMaxBot,
|
||||
float[] regionMinTop,
|
||||
boolean willMountViews) {
|
||||
mReactTag = reactTag;
|
||||
mDrawCommands = drawCommands;
|
||||
mDrawViewIndexMap = drawViewIndexMap;
|
||||
mCommandMaxBot = commandMaxBot;
|
||||
mCommandMinTop = commandMinTop;
|
||||
mAttachDetachListeners = listeners;
|
||||
mNodeRegions = nodeRegions;
|
||||
mRegionMaxBot = regionMaxBot;
|
||||
mRegionMinTop = regionMinTop;
|
||||
mWillMountViews = willMountViews;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.updateClippingMountState(
|
||||
mReactTag,
|
||||
mDrawCommands,
|
||||
mDrawViewIndexMap,
|
||||
mCommandMaxBot,
|
||||
mCommandMinTop,
|
||||
mAttachDetachListeners,
|
||||
mNodeRegions,
|
||||
mRegionMaxBot,
|
||||
mRegionMinTop,
|
||||
mWillMountViews);
|
||||
}
|
||||
}
|
||||
|
||||
private final class UpdateViewGroup implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final int[] mViewsToAdd;
|
||||
private final int[] mViewsToDetach;
|
||||
|
||||
private UpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
|
||||
mReactTag = reactTag;
|
||||
mViewsToAdd = viewsToAdd;
|
||||
mViewsToDetach = viewsToDetach;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.updateViewGroup(mReactTag, mViewsToAdd, mViewsToDetach);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UIOperation that updates View bounds for a View defined by reactTag.
|
||||
*/
|
||||
public final class UpdateViewBounds implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final int mLeft;
|
||||
private final int mTop;
|
||||
private final int mRight;
|
||||
private final int mBottom;
|
||||
|
||||
private UpdateViewBounds(int reactTag, int left, int top, int right, int bottom) {
|
||||
mReactTag = reactTag;
|
||||
mLeft = left;
|
||||
mTop = top;
|
||||
mRight = right;
|
||||
mBottom = bottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.updateViewBounds(mReactTag, mLeft, mTop, mRight, mBottom);
|
||||
}
|
||||
}
|
||||
|
||||
private final class SetPadding implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final int mPaddingLeft;
|
||||
private final int mPaddingTop;
|
||||
private final int mPaddingRight;
|
||||
private final int mPaddingBottom;
|
||||
|
||||
private SetPadding(
|
||||
int reactTag,
|
||||
int paddingLeft,
|
||||
int paddingTop,
|
||||
int paddingRight,
|
||||
int paddingBottom) {
|
||||
mReactTag = reactTag;
|
||||
mPaddingLeft = paddingLeft;
|
||||
mPaddingTop = paddingTop;
|
||||
mPaddingRight = paddingRight;
|
||||
mPaddingBottom = paddingBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.setPadding(
|
||||
mReactTag,
|
||||
mPaddingLeft,
|
||||
mPaddingTop,
|
||||
mPaddingRight,
|
||||
mPaddingBottom);
|
||||
}
|
||||
}
|
||||
|
||||
private final class DropViews implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final SparseIntArray mViewsToDrop;
|
||||
|
||||
private DropViews(ArrayList<Integer> viewsToDrop, ArrayList<Integer> parentsForViewsToDrop) {
|
||||
SparseIntArray sparseIntArray = new SparseIntArray();
|
||||
for (int i = 0, count = viewsToDrop.size(); i < count; i++) {
|
||||
sparseIntArray.put(viewsToDrop.get(i), parentsForViewsToDrop.get(i));
|
||||
}
|
||||
mViewsToDrop = sparseIntArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.dropViews(mViewsToDrop);
|
||||
}
|
||||
}
|
||||
|
||||
private final class MeasureVirtualView implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final float mScaledX;
|
||||
private final float mScaledY;
|
||||
private final float mScaledWidth;
|
||||
private final float mScaledHeight;
|
||||
private final Callback mCallback;
|
||||
private final boolean mRelativeToWindow;
|
||||
|
||||
private MeasureVirtualView(
|
||||
int reactTag,
|
||||
float scaledX,
|
||||
float scaledY,
|
||||
float scaledWidth,
|
||||
float scaledHeight,
|
||||
boolean relativeToWindow,
|
||||
Callback callback) {
|
||||
mReactTag = reactTag;
|
||||
mScaledX = scaledX;
|
||||
mScaledY = scaledY;
|
||||
mScaledWidth = scaledWidth;
|
||||
mScaledHeight = scaledHeight;
|
||||
mCallback = callback;
|
||||
mRelativeToWindow = relativeToWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
// Measure native View
|
||||
if (mRelativeToWindow) {
|
||||
// relative to the window
|
||||
mNativeViewHierarchyManager.measureInWindow(mReactTag, MEASURE_BUFFER);
|
||||
} else {
|
||||
// relative to the root view
|
||||
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
|
||||
}
|
||||
} catch (NoSuchNativeViewException noSuchNativeViewException) {
|
||||
// Invoke with no args to signal failure and to allow JS to clean up the callback
|
||||
// handle.
|
||||
mCallback.invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
float nativeViewX = MEASURE_BUFFER[0];
|
||||
float nativeViewY = MEASURE_BUFFER[1];
|
||||
float nativeViewWidth = MEASURE_BUFFER[2];
|
||||
float nativeViewHeight = MEASURE_BUFFER[3];
|
||||
|
||||
// Calculate size of the virtual child inside native View.
|
||||
float x = PixelUtil.toDIPFromPixel(mScaledX * nativeViewWidth + nativeViewX);
|
||||
float y = PixelUtil.toDIPFromPixel(mScaledY * nativeViewHeight + nativeViewY);
|
||||
float width = PixelUtil.toDIPFromPixel(mScaledWidth * nativeViewWidth);
|
||||
float height = PixelUtil.toDIPFromPixel(mScaledHeight * nativeViewHeight);
|
||||
|
||||
if (mRelativeToWindow) {
|
||||
mCallback.invoke(x, y, width, height);
|
||||
} else {
|
||||
mCallback.invoke(0, 0, width, height, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class DetachAllChildrenFromViews implements UIViewOperationQueue.UIOperation {
|
||||
private @Nullable int[] mViewsToDetachAllChildrenFrom;
|
||||
|
||||
public void setViewsToDetachAllChildrenFrom(int[] viewsToDetachAllChildrenFrom) {
|
||||
mViewsToDetachAllChildrenFrom = viewsToDetachAllChildrenFrom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.detachAllChildrenFromViews(mViewsToDetachAllChildrenFrom);
|
||||
}
|
||||
}
|
||||
|
||||
private final class FindTargetForTouchOperation implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final float mTargetX;
|
||||
private final float mTargetY;
|
||||
private final Callback mCallback;
|
||||
private final int[] NATIVE_VIEW_BUFFER = new int[1];
|
||||
|
||||
private FindTargetForTouchOperation(
|
||||
final int reactTag,
|
||||
final float targetX,
|
||||
final float targetY,
|
||||
final Callback callback) {
|
||||
super();
|
||||
mReactTag = reactTag;
|
||||
mTargetX = targetX;
|
||||
mTargetY = targetY;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
try {
|
||||
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
|
||||
} catch (IllegalViewOperationException e) {
|
||||
mCallback.invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// Because React coordinates are relative to root container, and measure() operates
|
||||
// on screen coordinates, we need to offset values using root container location.
|
||||
final float containerX = (float) MEASURE_BUFFER[0];
|
||||
final float containerY = (float) MEASURE_BUFFER[1];
|
||||
|
||||
View view = mNativeViewHierarchyManager.getView(mReactTag);
|
||||
final int touchTargetReactTag = TouchTargetHelper.findTargetTagForTouch(
|
||||
mTargetX,
|
||||
mTargetY,
|
||||
(ViewGroup) view,
|
||||
NATIVE_VIEW_BUFFER);
|
||||
|
||||
try {
|
||||
mNativeViewHierarchyManager.measure(
|
||||
NATIVE_VIEW_BUFFER[0],
|
||||
MEASURE_BUFFER);
|
||||
} catch (IllegalViewOperationException e) {
|
||||
mCallback.invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
NodeRegion region = NodeRegion.EMPTY;
|
||||
boolean isNativeView = NATIVE_VIEW_BUFFER[0] == touchTargetReactTag;
|
||||
if (!isNativeView) {
|
||||
// NATIVE_VIEW_BUFFER[0] is a FlatViewGroup, touchTargetReactTag is the touch target and
|
||||
// isn't an Android View - try to get its NodeRegion
|
||||
view = mNativeViewHierarchyManager.getView(NATIVE_VIEW_BUFFER[0]);
|
||||
if (view instanceof FlatViewGroup) {
|
||||
region = ((FlatViewGroup) view).getNodeRegionForTag(mReactTag);
|
||||
}
|
||||
}
|
||||
|
||||
int resultTag = region == NodeRegion.EMPTY ? touchTargetReactTag : region.mTag;
|
||||
float x = PixelUtil.toDIPFromPixel(region.getLeft() + MEASURE_BUFFER[0] - containerX);
|
||||
float y = PixelUtil.toDIPFromPixel(region.getTop() + MEASURE_BUFFER[1] - containerY);
|
||||
float width = PixelUtil.toDIPFromPixel(isNativeView ?
|
||||
MEASURE_BUFFER[2] : region.getRight() - region.getLeft());
|
||||
float height = PixelUtil.toDIPFromPixel(isNativeView ?
|
||||
MEASURE_BUFFER[3] : region.getBottom() - region.getTop());
|
||||
mCallback.invoke(resultTag, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to delay view manager command dispatch until after the view hierarchy is updated.
|
||||
* Mirrors command operation dispatch, but is only used in Nodes for view manager commands.
|
||||
*/
|
||||
public final class ViewManagerCommand implements UIViewOperationQueue.UIOperation {
|
||||
|
||||
private final int mReactTag;
|
||||
private final int mCommand;
|
||||
private final @Nullable ReadableArray mArgs;
|
||||
|
||||
public ViewManagerCommand(
|
||||
int reactTag,
|
||||
int command,
|
||||
@Nullable ReadableArray args) {
|
||||
mReactTag = reactTag;
|
||||
mCommand = command;
|
||||
mArgs = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.dispatchCommand(mReactTag, mCommand, mArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public FlatUIViewOperationQueue(
|
||||
ReactApplicationContext reactContext,
|
||||
FlatNativeViewHierarchyManager nativeViewHierarchyManager,
|
||||
int minTimeLeftInFrameForNonBatchedOperationMs) {
|
||||
super(reactContext, nativeViewHierarchyManager, minTimeLeftInFrameForNonBatchedOperationMs);
|
||||
|
||||
mNativeViewHierarchyManager = nativeViewHierarchyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
|
||||
*/
|
||||
public void enqueueUpdateMountState(
|
||||
int reactTag,
|
||||
@Nullable DrawCommand[] drawCommands,
|
||||
@Nullable AttachDetachListener[] listeners,
|
||||
@Nullable NodeRegion[] nodeRegions) {
|
||||
enqueueUIOperation(new UpdateMountState(
|
||||
reactTag,
|
||||
drawCommands,
|
||||
listeners,
|
||||
nodeRegions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
|
||||
*/
|
||||
public void enqueueUpdateClippingMountState(
|
||||
int reactTag,
|
||||
@Nullable DrawCommand[] drawCommands,
|
||||
SparseIntArray drawViewIndexMap,
|
||||
float[] commandMaxBot,
|
||||
float[] commandMinTop,
|
||||
@Nullable AttachDetachListener[] listeners,
|
||||
@Nullable NodeRegion[] nodeRegions,
|
||||
float[] regionMaxBot,
|
||||
float[] regionMinTop,
|
||||
boolean willMountViews) {
|
||||
enqueueUIOperation(new UpdateClippingMountState(
|
||||
reactTag,
|
||||
drawCommands,
|
||||
drawViewIndexMap,
|
||||
commandMaxBot,
|
||||
commandMinTop,
|
||||
listeners,
|
||||
nodeRegions,
|
||||
regionMaxBot,
|
||||
regionMinTop,
|
||||
willMountViews));
|
||||
}
|
||||
|
||||
public void enqueueUpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
|
||||
enqueueUIOperation(new UpdateViewGroup(reactTag, viewsToAdd, viewsToDetach));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new UIOperation that will update View bounds for a View defined by reactTag.
|
||||
*/
|
||||
public UpdateViewBounds createUpdateViewBounds(
|
||||
int reactTag,
|
||||
int left,
|
||||
int top,
|
||||
int right,
|
||||
int bottom) {
|
||||
return new UpdateViewBounds(reactTag, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public ViewManagerCommand createViewManagerCommand(
|
||||
int reactTag,
|
||||
int command,
|
||||
@Nullable ReadableArray args) {
|
||||
return new ViewManagerCommand(reactTag, command, args);
|
||||
}
|
||||
|
||||
/* package */ void enqueueFlatUIOperation(UIViewOperationQueue.UIOperation operation) {
|
||||
enqueueUIOperation(operation);
|
||||
}
|
||||
|
||||
public void enqueueSetPadding(
|
||||
int reactTag,
|
||||
int paddingLeft,
|
||||
int paddingTop,
|
||||
int paddingRight,
|
||||
int paddingBottom) {
|
||||
enqueueUIOperation(
|
||||
new SetPadding(reactTag, paddingLeft, paddingTop, paddingRight, paddingBottom));
|
||||
}
|
||||
|
||||
public void enqueueDropViews(
|
||||
ArrayList<Integer> viewsToDrop,
|
||||
ArrayList<Integer> parentsOfViewsToDrop) {
|
||||
enqueueUIOperation(new DropViews(viewsToDrop, parentsOfViewsToDrop));
|
||||
}
|
||||
|
||||
public void enqueueMeasureVirtualView(
|
||||
int reactTag,
|
||||
float scaledX,
|
||||
float scaledY,
|
||||
float scaledWidth,
|
||||
float scaledHeight,
|
||||
boolean relativeToWindow,
|
||||
Callback callback) {
|
||||
enqueueUIOperation(new MeasureVirtualView(
|
||||
reactTag,
|
||||
scaledX,
|
||||
scaledY,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
relativeToWindow,
|
||||
callback));
|
||||
}
|
||||
|
||||
public void enqueueProcessLayoutRequests() {
|
||||
enqueueUIOperation(mProcessLayoutRequests);
|
||||
}
|
||||
|
||||
public DetachAllChildrenFromViews enqueueDetachAllChildrenFromViews() {
|
||||
DetachAllChildrenFromViews op = new DetachAllChildrenFromViews();
|
||||
enqueueUIOperation(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueFindTargetForTouch(
|
||||
final int reactTag,
|
||||
final float targetX,
|
||||
final float targetY,
|
||||
final Callback callback) {
|
||||
enqueueUIOperation(
|
||||
new FindTargetForTouchOperation(reactTag, targetX, targetY, callback));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
|
||||
abstract class FlatViewManager extends ViewGroupManager<FlatViewGroup> {
|
||||
|
||||
@Override
|
||||
protected FlatViewGroup createViewInstance(ThemedReactContext reactContext) {
|
||||
return new FlatViewGroup(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(FlatViewGroup view, int backgroundColor) {
|
||||
// suppress
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllViews(FlatViewGroup parent) {
|
||||
parent.removeAllViewsInLayout();
|
||||
}
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.MetricAffectingSpan;
|
||||
|
||||
/* package */ final class FontStylingSpan extends MetricAffectingSpan {
|
||||
|
||||
/* package */ static final FontStylingSpan INSTANCE = new FontStylingSpan(
|
||||
Color.BLACK /* mTextColor */,
|
||||
0 /* mBackgroundColor */,
|
||||
-1 /* mFontSize */,
|
||||
-1 /* mFontStyle */,
|
||||
-1 /* mFontWeight */,
|
||||
false /* mHasUnderline */,
|
||||
false /* mHasStrikeThrough */,
|
||||
null /* mFontFamily */,
|
||||
true /* mFrozen */);
|
||||
|
||||
// text property
|
||||
private double mTextColor;
|
||||
private int mBackgroundColor;
|
||||
private boolean mHasUnderline;
|
||||
private boolean mHasStrikeThrough;
|
||||
|
||||
// font properties
|
||||
private int mFontSize;
|
||||
private int mFontStyle;
|
||||
private int mFontWeight;
|
||||
private @Nullable String mFontFamily;
|
||||
|
||||
// whether or not mutation is allowed.
|
||||
private boolean mFrozen;
|
||||
|
||||
FontStylingSpan() {
|
||||
}
|
||||
|
||||
private FontStylingSpan(
|
||||
double textColor,
|
||||
int backgroundColor,
|
||||
int fontSize,
|
||||
int fontStyle,
|
||||
int fontWeight,
|
||||
boolean hasUnderline,
|
||||
boolean hasStrikeThrough,
|
||||
@Nullable String fontFamily,
|
||||
boolean frozen) {
|
||||
mTextColor = textColor;
|
||||
mBackgroundColor = backgroundColor;
|
||||
mFontSize = fontSize;
|
||||
mFontStyle = fontStyle;
|
||||
mFontWeight = fontWeight;
|
||||
mHasUnderline = hasUnderline;
|
||||
mHasStrikeThrough = hasStrikeThrough;
|
||||
mFontFamily = fontFamily;
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
/* package */ FontStylingSpan mutableCopy() {
|
||||
return new FontStylingSpan(
|
||||
mTextColor,
|
||||
mBackgroundColor,
|
||||
mFontSize,
|
||||
mFontStyle,
|
||||
mFontWeight,
|
||||
mHasUnderline,
|
||||
mHasStrikeThrough,
|
||||
mFontFamily,
|
||||
false);
|
||||
}
|
||||
|
||||
/* package */ boolean isFrozen() {
|
||||
return mFrozen;
|
||||
}
|
||||
|
||||
/* package */ void freeze() {
|
||||
mFrozen = true;
|
||||
}
|
||||
|
||||
/* package */ double getTextColor() {
|
||||
return mTextColor;
|
||||
}
|
||||
|
||||
/* package */ void setTextColor(double textColor) {
|
||||
mTextColor = textColor;
|
||||
}
|
||||
|
||||
/* package */ int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
/* package */ void setBackgroundColor(int backgroundColor) {
|
||||
mBackgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
/* package */ int getFontSize() {
|
||||
return mFontSize;
|
||||
}
|
||||
|
||||
/* package */ void setFontSize(int fontSize) {
|
||||
mFontSize = fontSize;
|
||||
}
|
||||
|
||||
/* package */ int getFontStyle() {
|
||||
return mFontStyle;
|
||||
}
|
||||
|
||||
/* package */ void setFontStyle(int fontStyle) {
|
||||
mFontStyle = fontStyle;
|
||||
}
|
||||
|
||||
/* package */ int getFontWeight() {
|
||||
return mFontWeight;
|
||||
}
|
||||
|
||||
/* package */ void setFontWeight(int fontWeight) {
|
||||
mFontWeight = fontWeight;
|
||||
}
|
||||
|
||||
/* package */ @Nullable String getFontFamily() {
|
||||
return mFontFamily;
|
||||
}
|
||||
|
||||
/* package */ void setFontFamily(@Nullable String fontFamily) {
|
||||
mFontFamily = fontFamily;
|
||||
}
|
||||
|
||||
/* package */ boolean hasUnderline() {
|
||||
return mHasUnderline;
|
||||
}
|
||||
|
||||
/* package */ void setHasUnderline(boolean hasUnderline) {
|
||||
mHasUnderline = hasUnderline;
|
||||
}
|
||||
|
||||
/* package */ boolean hasStrikeThrough() {
|
||||
return mHasStrikeThrough;
|
||||
}
|
||||
|
||||
/* package */ void setHasStrikeThrough(boolean hasStrikeThrough) {
|
||||
mHasStrikeThrough = hasStrikeThrough;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
if (!Double.isNaN(mTextColor)) {
|
||||
ds.setColor((int) mTextColor);
|
||||
}
|
||||
|
||||
ds.bgColor = mBackgroundColor;
|
||||
ds.setUnderlineText(mHasUnderline);
|
||||
ds.setStrikeThruText(mHasStrikeThrough);
|
||||
updateMeasureState(ds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasureState(TextPaint ds) {
|
||||
if (mFontSize != -1) {
|
||||
ds.setTextSize(mFontSize);
|
||||
}
|
||||
|
||||
updateTypeface(ds);
|
||||
}
|
||||
|
||||
private int getNewStyle(int oldStyle) {
|
||||
int newStyle = oldStyle;
|
||||
if (mFontStyle != -1) {
|
||||
newStyle = (newStyle & ~Typeface.ITALIC) | mFontStyle;
|
||||
}
|
||||
|
||||
if (mFontWeight != -1) {
|
||||
newStyle = (newStyle & ~Typeface.BOLD) | mFontWeight;
|
||||
}
|
||||
|
||||
return newStyle;
|
||||
}
|
||||
|
||||
private void updateTypeface(TextPaint ds) {
|
||||
Typeface typeface = ds.getTypeface();
|
||||
|
||||
int oldStyle = (typeface == null) ? 0 : typeface.getStyle();
|
||||
int newStyle = getNewStyle(oldStyle);
|
||||
|
||||
if (oldStyle == newStyle && mFontFamily == null) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFontFamily != null) {
|
||||
typeface = TypefaceCache.getTypeface(mFontFamily, newStyle);
|
||||
} else {
|
||||
typeface = TypefaceCache.getTypeface(typeface, newStyle);
|
||||
}
|
||||
|
||||
ds.setTypeface(typeface);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
/**
|
||||
* NodeRegion that has a hit slop.
|
||||
*/
|
||||
/* package */ final class HitSlopNodeRegion extends NodeRegion {
|
||||
|
||||
private final Rect mHitSlop;
|
||||
|
||||
HitSlopNodeRegion(
|
||||
Rect hitSlop,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
int tag,
|
||||
boolean isVirtual) {
|
||||
super(left, top, right, bottom, tag, isVirtual);
|
||||
mHitSlop = hitSlop;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ float getTouchableLeft() {
|
||||
return getLeft() - mHitSlop.left;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ float getTouchableTop() {
|
||||
return getTop() - mHitSlop.top;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ float getTouchableRight() {
|
||||
return getRight() + mHitSlop.right;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ float getTouchableBottom() {
|
||||
return getBottom() + mHitSlop.bottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ boolean withinBounds(float touchX, float touchY) {
|
||||
return getTouchableLeft() <= touchX && touchX < getTouchableRight() &&
|
||||
getTouchableTop() <= touchY && touchY < getTouchableBottom();
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
/**
|
||||
* {@link DrawCommandManager} with horizontal clipping (The view scrolls left and right).
|
||||
*/
|
||||
/* package */ final class HorizontalDrawCommandManager extends ClippingDrawCommandManager {
|
||||
|
||||
/* package */ HorizontalDrawCommandManager(
|
||||
FlatViewGroup flatViewGroup,
|
||||
DrawCommand[] drawCommands) {
|
||||
super(flatViewGroup, drawCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
int commandStartIndex() {
|
||||
int start = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.left);
|
||||
// We don't care whether we matched or not, but positive indices are helpful. The binary search
|
||||
// returns ~index in the case that it isn't a match, so reverse that here.
|
||||
return start < 0 ? ~start : start;
|
||||
}
|
||||
|
||||
@Override
|
||||
int commandStopIndex(int start) {
|
||||
int stop = Arrays.binarySearch(
|
||||
mCommandMinTop,
|
||||
start,
|
||||
mCommandMinTop.length,
|
||||
mClippingRect.right);
|
||||
// We don't care whether we matched or not, but positive indices are helpful. The binary search
|
||||
// returns ~index in the case that it isn't a match, so reverse that here.
|
||||
return stop < 0 ? ~stop : stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
int regionStopIndex(float touchX, float touchY) {
|
||||
int stop = Arrays.binarySearch(mRegionMinTop, touchX + 0.0001f);
|
||||
// We don't care whether we matched or not, but positive indices are helpful. The binary search
|
||||
// returns ~index in the case that it isn't a match, so reverse that here.
|
||||
return stop < 0 ? ~stop : stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean regionAboveTouch(int index, float touchX, float touchY) {
|
||||
return mRegionMaxBottom[index] < touchX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the max and min arrays for a given set of node regions.
|
||||
*
|
||||
* This should never be called from the UI thread, as the reason it exists is to do work off the
|
||||
* UI thread.
|
||||
*
|
||||
* @param regions The regions that will eventually be mounted.
|
||||
* @param maxRight At each index i, the maximum right value of all regions at or below i.
|
||||
* @param minLeft At each index i, the minimum left value of all regions at or below i.
|
||||
*/
|
||||
public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxRight, float[] minLeft) {
|
||||
float last = 0;
|
||||
for (int i = 0; i < regions.length; i++) {
|
||||
last = Math.max(last, regions[i].getTouchableRight());
|
||||
maxRight[i] = last;
|
||||
}
|
||||
for (int i = regions.length - 1; i >= 0; i--) {
|
||||
last = Math.min(last, regions[i].getTouchableLeft());
|
||||
minLeft[i] = last;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the max and min arrays for a given set of draw commands. Also populates a mapping of
|
||||
* react tags to their index position in the command array.
|
||||
*
|
||||
* This should never be called from the UI thread, as the reason it exists is to do work off the
|
||||
* UI thread.
|
||||
*
|
||||
* @param commands The draw commands that will eventually be mounted.
|
||||
* @param maxRight At each index i, the maximum right value of all draw commands at or below i.
|
||||
* @param minLeft At each index i, the minimum left value of all draw commands at or below i.
|
||||
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
|
||||
*/
|
||||
public static void fillMaxMinArrays(
|
||||
DrawCommand[] commands,
|
||||
float[] maxRight,
|
||||
float[] minLeft,
|
||||
SparseIntArray drawViewIndexMap) {
|
||||
float last = 0;
|
||||
// Loop through the DrawCommands, keeping track of the maximum we've seen if we only iterated
|
||||
// through items up to this position.
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
if (commands[i] instanceof DrawView) {
|
||||
DrawView drawView = (DrawView) commands[i];
|
||||
// These will generally be roughly sorted by id, so try to insert at the end if possible.
|
||||
drawViewIndexMap.append(drawView.reactTag, i);
|
||||
last = Math.max(last, drawView.mLogicalRight);
|
||||
} else {
|
||||
last = Math.max(last, commands[i].getRight());
|
||||
}
|
||||
maxRight[i] = last;
|
||||
}
|
||||
// Intentionally leave last as it was, since it's at the maximum bottom position we've seen so
|
||||
// far, we can use it again.
|
||||
|
||||
// Loop through backwards, keeping track of the minimum we've seen at this position.
|
||||
for (int i = commands.length - 1; i >= 0; i--) {
|
||||
if (commands[i] instanceof DrawView) {
|
||||
last = Math.min(last, ((DrawView) commands[i]).mLogicalLeft);
|
||||
} else {
|
||||
last = Math.min(last, commands[i].getLeft());
|
||||
}
|
||||
minLeft[i] = last;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.text.style.ReplacementSpan;
|
||||
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/* package */ final class InlineImageSpanWithPipeline extends ReplacementSpan
|
||||
implements AttachDetachListener, BitmapUpdateListener {
|
||||
|
||||
private static final RectF TMP_RECT = new RectF();
|
||||
|
||||
private @Nullable PipelineRequestHelper mRequestHelper;
|
||||
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
||||
private float mWidth;
|
||||
private float mHeight;
|
||||
private boolean mFrozen;
|
||||
|
||||
/* package */ InlineImageSpanWithPipeline() {
|
||||
this(null, Float.NaN, Float.NaN);
|
||||
}
|
||||
|
||||
private InlineImageSpanWithPipeline(
|
||||
@Nullable PipelineRequestHelper requestHelper,
|
||||
float width,
|
||||
float height) {
|
||||
mRequestHelper = requestHelper;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
/* package */ InlineImageSpanWithPipeline mutableCopy() {
|
||||
return new InlineImageSpanWithPipeline(mRequestHelper, mWidth, mHeight);
|
||||
}
|
||||
|
||||
/* package */ boolean hasImageRequest() {
|
||||
return mRequestHelper != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a new image request to the DrawImage, or null to clear the image request.
|
||||
*/
|
||||
/* package */ void setImageRequest(@Nullable ImageRequest imageRequest) {
|
||||
if (imageRequest == null) {
|
||||
mRequestHelper = null;
|
||||
} else {
|
||||
mRequestHelper = new PipelineRequestHelper(imageRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ float getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/* package */ void setWidth(float width) {
|
||||
mWidth = width;
|
||||
}
|
||||
|
||||
/* package */ float getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
/* package */ void setHeight(float height) {
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
/* package */ void freeze() {
|
||||
mFrozen = true;
|
||||
}
|
||||
|
||||
/* package */ boolean isFrozen() {
|
||||
return mFrozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecondaryAttach(Bitmap bitmap) {
|
||||
// We don't know if width or height changed, so invalidate just in case.
|
||||
Assertions.assumeNotNull(mCallback).invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapReady(Bitmap bitmap) {
|
||||
// Bitmap is now ready, draw it.
|
||||
Assertions.assumeNotNull(mCallback).invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageLoadEvent(int imageLoadEvent) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
||||
mCallback = callback;
|
||||
|
||||
if (mRequestHelper != null) {
|
||||
mRequestHelper.attach(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetached() {
|
||||
if (mRequestHelper != null) {
|
||||
mRequestHelper.detach();
|
||||
|
||||
if (mRequestHelper.isDetached()) {
|
||||
// optional
|
||||
mCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||
if (fm != null) {
|
||||
fm.ascent = -Math.round(mHeight);
|
||||
fm.descent = 0;
|
||||
|
||||
fm.top = fm.ascent;
|
||||
fm.bottom = 0;
|
||||
}
|
||||
|
||||
return Math.round(mWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(
|
||||
Canvas canvas,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
float x,
|
||||
int top,
|
||||
int y,
|
||||
int bottom,
|
||||
Paint paint) {
|
||||
if (mRequestHelper == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap bitmap = mRequestHelper.getBitmap();
|
||||
if (bitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float bottomFloat = (float) bottom - paint.getFontMetricsInt().descent;
|
||||
TMP_RECT.set(x, bottomFloat - mHeight, x + mWidth, bottomFloat);
|
||||
|
||||
canvas.drawBitmap(bitmap, null, TMP_RECT, paint);
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Helper class that sorts moveFrom/moveTo arrays in lockstep.
|
||||
*/
|
||||
/* package */ final class MoveProxy {
|
||||
|
||||
private @Nullable ReadableArray mMoveTo;
|
||||
private int mSize;
|
||||
private int[] mMapping = new int[8];
|
||||
private ReactShadowNode[] mChildren = new ReactShadowNodeImpl[4];
|
||||
|
||||
/**
|
||||
* Returns size of underlying moveTo/moveFrom arrays
|
||||
*/
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns ith child that we want to move if moveFrom was sorted.
|
||||
*/
|
||||
public void setChildMoveFrom(int moveFromIndex, ReactShadowNode node) {
|
||||
mChildren[moveFromToIndex(moveFromIndex)] = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ith child that we want to move if moveTo was sorted.
|
||||
*/
|
||||
public ReactShadowNode getChildMoveTo(int moveToIndex) {
|
||||
return mChildren[moveToToIndex(moveToIndex)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of the ith child that we want to move if moveFrom was sorted
|
||||
*/
|
||||
public int getMoveFrom(int moveFromIndex) {
|
||||
return moveFromToValue(moveFromIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of the ith child that we want to move to if moveTo was sorted
|
||||
*/
|
||||
public int getMoveTo(int moveToIndex) {
|
||||
return moveToToValue(moveToIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize MoveProxy with given moveFrom and moveTo arrays.
|
||||
*/
|
||||
public void setup(ReadableArray moveFrom, ReadableArray moveTo) {
|
||||
mMoveTo = moveTo;
|
||||
|
||||
if (moveFrom == null) {
|
||||
setSize(0);
|
||||
return;
|
||||
}
|
||||
|
||||
int size = moveFrom.size();
|
||||
int requiredSpace = size + size;
|
||||
if (mMapping.length < requiredSpace) {
|
||||
mMapping = new int[requiredSpace];
|
||||
mChildren = new FlatShadowNode[size];
|
||||
}
|
||||
|
||||
setSize(size);
|
||||
|
||||
// Array contains data in the following way:
|
||||
// [ k0, v0, k1, v1, k2, v2, ... ]
|
||||
//
|
||||
// where vi = moveFrom.getInt(ki)
|
||||
|
||||
// We don't technically *need* to store vi, but they are accessed so often that it makes sense
|
||||
// to cache it instead of calling ReadableArray.getInt() all the time.
|
||||
|
||||
// Sorting algorithm will reorder ki/vi pairs in such a way that vi < v(i+1)
|
||||
|
||||
// Code below is an insertion sort, adapted from DualPivotQuicksort.doSort()
|
||||
|
||||
// At each step i, we got the following data:
|
||||
|
||||
// [k0, v0, k1, v2, .. k(i-1), v(i-1), unused...]
|
||||
// where v0 < v1 < v2 ... < v(i-1)
|
||||
//
|
||||
// This holds true for step i = 0 (array of size one is sorted)
|
||||
// Again, k0 = 0, v0 = moveFrom.getInt(k0)
|
||||
setKeyValue(0, 0, moveFrom.getInt(0));
|
||||
|
||||
// At each of the next steps, we grab a new key and walk back until we find first key that is
|
||||
// less than current, shifting key/value pairs if they are larger than current key.
|
||||
for (int i = 1; i < size; i++) {
|
||||
// this is our next key
|
||||
int current = moveFrom.getInt(i);
|
||||
|
||||
// this loop will find correct position for it
|
||||
int j;
|
||||
|
||||
// At this point, array is like this: [ k0, v0, k1, v1, k2, v2, ..., k(i-1), v(i-1), ... ]
|
||||
for (j = i - 1; j >= 0; j--) {
|
||||
if (moveFromToValue(j) < current) {
|
||||
break;
|
||||
}
|
||||
|
||||
// value at index j is < current value, shift that value and its key
|
||||
setKeyValue(j + 1, moveFromToIndex(j), moveFromToValue(j));
|
||||
}
|
||||
|
||||
setKeyValue(j + 1, i, current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of ith key in array.
|
||||
*/
|
||||
private static int k(int i) {
|
||||
return i * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of ith value in array.
|
||||
*/
|
||||
private static int v(int i) {
|
||||
return i * 2 + 1;
|
||||
}
|
||||
|
||||
private void setKeyValue(int index, int key, int value) {
|
||||
mMapping[k(index)] = key;
|
||||
mMapping[v(index)] = value;
|
||||
}
|
||||
|
||||
private int moveFromToIndex(int index) {
|
||||
return mMapping[k(index)];
|
||||
}
|
||||
|
||||
private int moveFromToValue(int index) {
|
||||
return mMapping[v(index)];
|
||||
}
|
||||
|
||||
private static int moveToToIndex(int index) {
|
||||
return index;
|
||||
}
|
||||
|
||||
private int moveToToValue(int index) {
|
||||
return Assertions.assumeNotNull(mMoveTo).getInt(index);
|
||||
}
|
||||
|
||||
private void setSize(int newSize) {
|
||||
// reset references to null when shrinking to avoid memory leaks
|
||||
for (int i = newSize; i < mSize; ++i) {
|
||||
mChildren[i] = null;
|
||||
}
|
||||
|
||||
mSize = newSize;
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaUnit;
|
||||
import com.facebook.yoga.YogaValue;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/* package */ final class NativeViewWrapper extends FlatShadowNode implements AndroidView {
|
||||
|
||||
@Nullable
|
||||
private final ReactShadowNode mReactShadowNode;
|
||||
private final boolean mNeedsCustomLayoutForChildren;
|
||||
private boolean mPaddingChanged = false;
|
||||
private boolean mForceMountGrandChildrenToView;
|
||||
|
||||
/* package */ NativeViewWrapper(ViewManager viewManager) {
|
||||
ReactShadowNode reactShadowNode = viewManager.createShadowNodeInstance();
|
||||
if (reactShadowNode instanceof YogaMeasureFunction) {
|
||||
mReactShadowNode = reactShadowNode;
|
||||
setMeasureFunction((YogaMeasureFunction) reactShadowNode);
|
||||
} else {
|
||||
mReactShadowNode = null;
|
||||
}
|
||||
|
||||
if (viewManager instanceof ViewGroupManager) {
|
||||
ViewGroupManager viewGroupManager = (ViewGroupManager) viewManager;
|
||||
mNeedsCustomLayoutForChildren = viewGroupManager.needsCustomLayoutForChildren();
|
||||
mForceMountGrandChildrenToView = viewGroupManager.shouldPromoteGrandchildren();
|
||||
} else {
|
||||
mNeedsCustomLayoutForChildren = false;
|
||||
}
|
||||
|
||||
forceMountToView();
|
||||
forceMountChildrenToView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return mNeedsCustomLayoutForChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaddingChanged() {
|
||||
return mPaddingChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPaddingChanged() {
|
||||
mPaddingChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
// suppress, this is handled by a ViewManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReactTag(int reactTag) {
|
||||
super.setReactTag(reactTag);
|
||||
if (mReactShadowNode != null) {
|
||||
mReactShadowNode.setReactTag(reactTag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThemedContext(ThemedReactContext themedContext) {
|
||||
super.setThemedContext(themedContext);
|
||||
|
||||
if (mReactShadowNode != null) {
|
||||
mReactShadowNode.setThemedContext(themedContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package*/ void handleUpdateProperties(ReactStylesDiffMap styles) {
|
||||
if (mReactShadowNode != null) {
|
||||
mReactShadowNode.updateProperties(styles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChildAt(ReactShadowNodeImpl child, int i) {
|
||||
super.addChildAt(child, i);
|
||||
if (mForceMountGrandChildrenToView && child instanceof FlatShadowNode) {
|
||||
((FlatShadowNode) child).forceMountChildrenToView();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int spacingType, float padding) {
|
||||
YogaValue current = getStylePadding(spacingType);
|
||||
if (current.unit != YogaUnit.POINT || current.value != padding) {
|
||||
super.setPadding(spacingType, padding);
|
||||
mPaddingChanged = true;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaddingPercent(int spacingType, float percent) {
|
||||
YogaValue current = getStylePadding(spacingType);
|
||||
if (current.unit != YogaUnit.PERCENT || current.value != percent) {
|
||||
super.setPadding(spacingType, percent);
|
||||
mPaddingChanged = true;
|
||||
markUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
|
||||
if (mReactShadowNode != null && mReactShadowNode.hasUnseenUpdates()) {
|
||||
mReactShadowNode.onCollectExtraUpdates(uiViewOperationQueue);
|
||||
markUpdateSeen();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
/* package */ class NodeRegion {
|
||||
/* package */ static final NodeRegion[] EMPTY_ARRAY = new NodeRegion[0];
|
||||
/* package */ static final NodeRegion EMPTY = new NodeRegion(0, 0, 0, 0, -1, false);
|
||||
|
||||
private final float mLeft;
|
||||
private final float mTop;
|
||||
private final float mRight;
|
||||
private final float mBottom;
|
||||
/* package */ final int mTag;
|
||||
/* package */ final boolean mIsVirtual;
|
||||
|
||||
/* package */ NodeRegion(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
int tag,
|
||||
boolean isVirtual) {
|
||||
mLeft = left;
|
||||
mTop = top;
|
||||
mRight = right;
|
||||
mBottom = bottom;
|
||||
mTag = tag;
|
||||
mIsVirtual = isVirtual;
|
||||
}
|
||||
|
||||
/* package */ final boolean matches(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
boolean isVirtual) {
|
||||
return left == mLeft && top == mTop && right == mRight && bottom == mBottom &&
|
||||
isVirtual == mIsVirtual;
|
||||
}
|
||||
|
||||
/**
|
||||
* The left bound of the underlying node.
|
||||
*
|
||||
* @return The node bound.
|
||||
*/
|
||||
/* package */ final float getLeft() {
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* The top bound of the underlying node.
|
||||
*
|
||||
* @return The node bound.
|
||||
*/
|
||||
/* package */ final float getTop() {
|
||||
return mTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* The right bound of the underlying node.
|
||||
*
|
||||
* @return The node bound.
|
||||
*/
|
||||
/* package */ final float getRight() {
|
||||
return mRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bottom bound of the underlying node.
|
||||
*
|
||||
* @return The node bound.
|
||||
*/
|
||||
/* package */ final float getBottom() {
|
||||
return mBottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* The left bound of the region for the purpose of touch. This is usually the bound of the
|
||||
* underlying node, except in the case of hit slop.
|
||||
*
|
||||
* @return The touch bound.
|
||||
*/
|
||||
/* package */ float getTouchableLeft() {
|
||||
return getLeft();
|
||||
}
|
||||
|
||||
/**
|
||||
* The top bound of the region for the purpose of touch. This is usually the bound of the
|
||||
* underlying node, except in the case of hit slop.
|
||||
*
|
||||
* @return The touch bound.
|
||||
*/
|
||||
/* package */ float getTouchableTop() {
|
||||
return getTop();
|
||||
}
|
||||
|
||||
/**
|
||||
* The right bound of the region for the purpose of touch. This is usually the bound of the
|
||||
* underlying node, except in the case of hit slop.
|
||||
*
|
||||
* @return The touch bound.
|
||||
*/
|
||||
/* package */ float getTouchableRight() {
|
||||
return getRight();
|
||||
}
|
||||
|
||||
/**
|
||||
* The bottom bound of the region for the purpose of touch. This is usually the bound of the
|
||||
* underlying node, except in the case of hit slop.
|
||||
*
|
||||
* @return The touch bound.
|
||||
*/
|
||||
/* package */ float getTouchableBottom() {
|
||||
return getBottom();
|
||||
}
|
||||
|
||||
/* package */ boolean withinBounds(float touchX, float touchY) {
|
||||
return mLeft <= touchX && touchX < mRight && mTop <= touchY && touchY < mBottom;
|
||||
}
|
||||
|
||||
/* package */ int getReactTag(float touchX, float touchY) {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
/* package */ boolean matchesTag(int tag) {
|
||||
return mTag == tag;
|
||||
}
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
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;
|
||||
import com.facebook.react.views.image.ImageLoadEvent;
|
||||
|
||||
/**
|
||||
* 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 PipelineRequestHelper
|
||||
implements DataSubscriber<CloseableReference<CloseableImage>> {
|
||||
|
||||
private final ImageRequest mImageRequest;
|
||||
private @Nullable BitmapUpdateListener mBitmapUpdateListener;
|
||||
private @Nullable DataSource<CloseableReference<CloseableImage>> mDataSource;
|
||||
private @Nullable CloseableReference<CloseableImage> mImageRef;
|
||||
private int mAttachCounter;
|
||||
|
||||
/* package */ PipelineRequestHelper(ImageRequest imageRequest) {
|
||||
mImageRequest = imageRequest;
|
||||
}
|
||||
|
||||
/* package */ void attach(BitmapUpdateListener listener) {
|
||||
mBitmapUpdateListener = listener;
|
||||
|
||||
mAttachCounter++;
|
||||
if (mAttachCounter != 1) {
|
||||
// this is a secondary attach, ignore it, only updating Bitmap boundaries if needed.
|
||||
Bitmap bitmap = getBitmap();
|
||||
if (bitmap != null) {
|
||||
listener.onSecondaryAttach(bitmap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_START);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
mBitmapUpdateListener = 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();
|
||||
}
|
||||
|
||||
/* package */ boolean isDetached() {
|
||||
return mAttachCounter == 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
BitmapUpdateListener listener = Assertions.assumeNotNull(mBitmapUpdateListener);
|
||||
listener.onBitmapReady(bitmap);
|
||||
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD);
|
||||
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||
if (mDataSource == dataSource) {
|
||||
Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_ERROR);
|
||||
Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
|
||||
mDataSource = null;
|
||||
}
|
||||
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||
if (mDataSource == dataSource) {
|
||||
mDataSource = null;
|
||||
}
|
||||
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
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 with http:// or https://) or a local resource (a BitmapDrawable).
|
||||
*/
|
||||
/* package */ class RCTImageView<T extends AbstractDrawCommand & DrawImage> extends FlatShadowNode {
|
||||
|
||||
static Object sCallerContext = RCTImageView.class;
|
||||
|
||||
/**
|
||||
* Assigns 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,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
super.collectState(
|
||||
stateBuilder,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
|
||||
if (mDrawImage.hasImageRequest()) {
|
||||
mDrawImage = (T) mDrawImage.updateBoundsAndFreeze(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
stateBuilder.addDrawCommand(mDrawImage);
|
||||
stateBuilder.addAttachDetachListener(mDrawImage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean doesDraw() {
|
||||
return mDrawImage.hasImageRequest() || super.doesDraw();
|
||||
}
|
||||
|
||||
@ReactProp(name = "shouldNotifyLoadEvents")
|
||||
public void setShouldNotifyLoadEvents(boolean shouldNotifyLoadEvents) {
|
||||
getMutableDrawImage().setReactTag(shouldNotifyLoadEvents ? getReactTag() : 0);
|
||||
}
|
||||
|
||||
@ReactProp(name = "src")
|
||||
public void setSource(@Nullable ReadableArray sources) {
|
||||
getMutableDrawImage().setSource(getThemedContext(), sources);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "borderColor", customType = "Color")
|
||||
public void setBorderColor(int borderColor) {
|
||||
if (mDrawImage.getBorderColor() != borderColor) {
|
||||
getMutableDrawImage().setBorderColor(borderColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorder(int spacingType, float borderWidth) {
|
||||
super.setBorder(spacingType, borderWidth);
|
||||
|
||||
if (spacingType == Spacing.ALL && mDrawImage.getBorderWidth() != borderWidth) {
|
||||
getMutableDrawImage().setBorderWidth(borderWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "borderRadius")
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
if (mDrawImage.getBorderRadius() != borderRadius) {
|
||||
getMutableDrawImage().setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "fadeDuration")
|
||||
public void setFadeDuration(int durationMs) {
|
||||
getMutableDrawImage().setFadeDuration(durationMs);
|
||||
}
|
||||
|
||||
@ReactProp(name = "progressiveRenderingEnabled")
|
||||
public void setProgressiveRenderingEnabled(boolean enabled) {
|
||||
getMutableDrawImage().setProgressiveRenderingEnabled(enabled);
|
||||
}
|
||||
|
||||
private T getMutableDrawImage() {
|
||||
if (mDrawImage.isFrozen()) {
|
||||
mDrawImage = (T) mDrawImage.mutableCopy();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
return mDrawImage;
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
|
||||
import com.facebook.react.views.image.GlobalImageLoadListener;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class RCTImageViewManager extends FlatViewManager {
|
||||
|
||||
/* package */ static final String REACT_CLASS = "RCTImageView";
|
||||
|
||||
private @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private @Nullable GlobalImageLoadListener mGlobalImageLoadListener;
|
||||
private final @Nullable Object mCallerContext;
|
||||
|
||||
public RCTImageViewManager() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public RCTImageViewManager(
|
||||
AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
Object callerContext) {
|
||||
this(draweeControllerBuilder, null, callerContext);
|
||||
}
|
||||
|
||||
public RCTImageViewManager(
|
||||
AbstractDraweeControllerBuilder draweeControllerBuilder,
|
||||
@Nullable GlobalImageLoadListener globalImageLoadListener,
|
||||
Object callerContext) {
|
||||
mDraweeControllerBuilder = draweeControllerBuilder;
|
||||
mGlobalImageLoadListener = globalImageLoadListener;
|
||||
mCallerContext = callerContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTImageView createShadowNodeInstance() {
|
||||
return new RCTImageView(new DrawImageWithDrawee(mGlobalImageLoadListener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTImageView> getShadowNodeClass() {
|
||||
return RCTImageView.class;
|
||||
}
|
||||
|
||||
public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
|
||||
if (mDraweeControllerBuilder == null) {
|
||||
mDraweeControllerBuilder = Fresco.newDraweeControllerBuilder();
|
||||
}
|
||||
return mDraweeControllerBuilder;
|
||||
}
|
||||
|
||||
public Object getCallerContext() {
|
||||
return mCallerContext;
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.views.modal.ReactModalHostManager;
|
||||
|
||||
public class RCTModalHostManager extends ReactModalHostManager {
|
||||
|
||||
/* package */ static final String REACT_CLASS = ReactModalHostManager.REACT_CLASS;
|
||||
|
||||
@Override
|
||||
public LayoutShadowNode createShadowNodeInstance() {
|
||||
return new FlatReactModalShadowNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends LayoutShadowNode> getShadowNodeClass() {
|
||||
return FlatReactModalShadowNode.class;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
/**
|
||||
* RCTRawText is a FlatTextShadowNode that can only contain raw text (but not styling).
|
||||
*/
|
||||
/* package */ final class RCTRawText extends FlatTextShadowNode {
|
||||
|
||||
private @Nullable String mText;
|
||||
|
||||
@Override
|
||||
protected void performCollectText(SpannableStringBuilder builder) {
|
||||
if (mText != null) {
|
||||
builder.append(mText);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performApplySpans(
|
||||
SpannableStringBuilder builder,
|
||||
int begin,
|
||||
int end,
|
||||
boolean isEditable) {
|
||||
builder.setSpan(
|
||||
this,
|
||||
begin,
|
||||
end,
|
||||
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@ReactProp(name = "text")
|
||||
public void setText(@Nullable String text) {
|
||||
mText = text;
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
/**
|
||||
* ViewManager that creates instances of RCTRawText.
|
||||
*/
|
||||
public final class RCTRawTextManager extends VirtualViewManager<RCTRawText> {
|
||||
|
||||
/* package */ static final String REACT_CLASS = "RCTRawText";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTRawText createShadowNodeInstance() {
|
||||
return new RCTRawText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTRawText> getShadowNodeClass() {
|
||||
return RCTRawText.class;
|
||||
}
|
||||
}
|
|
@ -1,360 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.support.v4.text.TextDirectionHeuristicsCompat;
|
||||
import android.text.Layout;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
|
||||
import com.facebook.yoga.YogaDirection;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
import com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder;
|
||||
import com.facebook.fbui.textlayoutbuilder.glyphwarmer.GlyphWarmerImpl;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
/**
|
||||
* RCTText is a top-level node for text. It extends {@link RCTVirtualText} because it can contain
|
||||
* styling information, but has the following differences:
|
||||
*
|
||||
* a) RCTText is not a virtual node, and can be measured and laid out.
|
||||
* b) when no font size is specified, a font size of ViewDefaults#FONT_SIZE_SP is assumed.
|
||||
*/
|
||||
/* package */ final class RCTText extends RCTVirtualText implements YogaMeasureFunction {
|
||||
|
||||
// index of left and right in the Layout.Alignment enum since the base values are @hide
|
||||
private static final int ALIGNMENT_LEFT = 3;
|
||||
private static final int ALIGNMENT_RIGHT = 4;
|
||||
|
||||
// We set every value we use every time we use the layout builder, so we can get away with only
|
||||
// using a single instance.
|
||||
private static final TextLayoutBuilder sTextLayoutBuilder =
|
||||
new TextLayoutBuilder()
|
||||
.setShouldCacheLayout(false)
|
||||
.setShouldWarmText(true)
|
||||
.setGlyphWarmer(new GlyphWarmerImpl());
|
||||
|
||||
private @Nullable CharSequence mText;
|
||||
private @Nullable DrawTextLayout mDrawCommand;
|
||||
private float mSpacingMult = 1.0f;
|
||||
private float mSpacingAdd = 0.0f;
|
||||
private int mNumberOfLines = Integer.MAX_VALUE;
|
||||
private int mAlignment = Gravity.NO_GRAVITY;
|
||||
private boolean mIncludeFontPadding = true;
|
||||
|
||||
public RCTText() {
|
||||
setMeasureFunction(this);
|
||||
getSpan().setFontSize(getDefaultFontSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtualAnchor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long measure(
|
||||
YogaNode node,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode) {
|
||||
|
||||
CharSequence text = getText();
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
// to indicate that we don't have anything to display
|
||||
mText = null;
|
||||
return YogaMeasureOutput.make(0, 0);
|
||||
} else {
|
||||
mText = text;
|
||||
}
|
||||
|
||||
Layout layout = createTextLayout(
|
||||
(int) Math.ceil(width),
|
||||
widthMode,
|
||||
TextUtils.TruncateAt.END,
|
||||
mIncludeFontPadding,
|
||||
mNumberOfLines,
|
||||
mNumberOfLines == 1,
|
||||
text,
|
||||
getFontSize(),
|
||||
mSpacingAdd,
|
||||
mSpacingMult,
|
||||
getFontStyle(),
|
||||
getAlignment());
|
||||
|
||||
if (mDrawCommand != null && !mDrawCommand.isFrozen()) {
|
||||
mDrawCommand.setLayout(layout);
|
||||
} else {
|
||||
mDrawCommand = new DrawTextLayout(layout);
|
||||
}
|
||||
|
||||
return YogaMeasureOutput.make(mDrawCommand.getLayoutWidth(), mDrawCommand.getLayoutHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void collectState(
|
||||
StateBuilder stateBuilder,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
|
||||
super.collectState(
|
||||
stateBuilder,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
|
||||
if (mText == null) {
|
||||
// as an optimization, LayoutEngine may not call measure in certain cases, such as when the
|
||||
// dimensions are already defined. in these cases, we should still draw the text.
|
||||
if (bottom - top > 0 && right - left > 0) {
|
||||
CharSequence text = getText();
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
mText = text;
|
||||
}
|
||||
}
|
||||
|
||||
if (mText == null) {
|
||||
// nothing to draw (empty text).
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean updateNodeRegion = false;
|
||||
if (mDrawCommand == null) {
|
||||
mDrawCommand = new DrawTextLayout(createTextLayout(
|
||||
(int) Math.ceil(right - left),
|
||||
YogaMeasureMode.EXACTLY,
|
||||
TextUtils.TruncateAt.END,
|
||||
mIncludeFontPadding,
|
||||
mNumberOfLines,
|
||||
mNumberOfLines == 1,
|
||||
mText,
|
||||
getFontSize(),
|
||||
mSpacingAdd,
|
||||
mSpacingMult,
|
||||
getFontStyle(),
|
||||
getAlignment()));
|
||||
updateNodeRegion = true;
|
||||
}
|
||||
|
||||
left += getPadding(Spacing.LEFT);
|
||||
top += getPadding(Spacing.TOP);
|
||||
|
||||
// these are actual right/bottom coordinates where this DrawCommand will draw.
|
||||
right = left + mDrawCommand.getLayoutWidth();
|
||||
bottom = top + mDrawCommand.getLayoutHeight();
|
||||
|
||||
mDrawCommand = (DrawTextLayout) mDrawCommand.updateBoundsAndFreeze(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
stateBuilder.addDrawCommand(mDrawCommand);
|
||||
|
||||
if (updateNodeRegion) {
|
||||
NodeRegion nodeRegion = getNodeRegion();
|
||||
if (nodeRegion instanceof TextNodeRegion) {
|
||||
((TextNodeRegion) nodeRegion).setLayout(mDrawCommand.getLayout());
|
||||
}
|
||||
}
|
||||
|
||||
performCollectAttachDetachListeners(stateBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean doesDraw() {
|
||||
// assume text always draws - this is a performance optimization to avoid having to
|
||||
// getText() when layout was skipped and when collectState wasn't yet called
|
||||
return true;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.LINE_HEIGHT, defaultDouble = Double.NaN)
|
||||
public void setLineHeight(double lineHeight) {
|
||||
if (Double.isNaN(lineHeight)) {
|
||||
mSpacingMult = 1.0f;
|
||||
mSpacingAdd = 0.0f;
|
||||
} else {
|
||||
mSpacingMult = 0.0f;
|
||||
mSpacingAdd = PixelUtil.toPixelFromSP((float) lineHeight);
|
||||
}
|
||||
notifyChanged(true);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = Integer.MAX_VALUE)
|
||||
public void setNumberOfLines(int numberOfLines) {
|
||||
mNumberOfLines = numberOfLines;
|
||||
notifyChanged(true);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true)
|
||||
public void setIncludeFontPadding(boolean includepad) {
|
||||
mIncludeFontPadding = includepad;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ void updateNodeRegion(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
boolean isVirtual) {
|
||||
|
||||
NodeRegion nodeRegion = getNodeRegion();
|
||||
if (mDrawCommand == null) {
|
||||
if (!nodeRegion.matches(left, top, right, bottom, isVirtual)) {
|
||||
setNodeRegion(new TextNodeRegion(left, top, right, bottom, getReactTag(), isVirtual, null));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Layout layout = null;
|
||||
|
||||
if (nodeRegion instanceof TextNodeRegion) {
|
||||
layout = ((TextNodeRegion) nodeRegion).getLayout();
|
||||
}
|
||||
|
||||
Layout newLayout = mDrawCommand.getLayout();
|
||||
if (!nodeRegion.matches(left, top, right, bottom, isVirtual) || layout != newLayout) {
|
||||
setNodeRegion(
|
||||
new TextNodeRegion(left, top, right, bottom, getReactTag(), isVirtual, newLayout));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultFontSize() {
|
||||
// top-level <Text /> should always specify font size.
|
||||
return fontSizeFromSp(ViewDefaults.FONT_SIZE_SP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyChanged(boolean shouldRemeasure) {
|
||||
// Future patch: should only recreate Layout if shouldRemeasure is false
|
||||
dirty();
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_ALIGN)
|
||||
public void setTextAlign(@Nullable String textAlign) {
|
||||
if (textAlign == null || "auto".equals(textAlign)) {
|
||||
mAlignment = Gravity.NO_GRAVITY;
|
||||
} else if ("left".equals(textAlign)) {
|
||||
// left and right may yield potentially different results (relative to non-nodes) in cases
|
||||
// when supportsRTL="true" in the manifest.
|
||||
mAlignment = Gravity.LEFT;
|
||||
} else if ("right".equals(textAlign)) {
|
||||
mAlignment = Gravity.RIGHT;
|
||||
} else if ("center".equals(textAlign)) {
|
||||
mAlignment = Gravity.CENTER;
|
||||
} else {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
|
||||
}
|
||||
|
||||
notifyChanged(false);
|
||||
}
|
||||
|
||||
public Layout.Alignment getAlignment() {
|
||||
boolean isRtl = getLayoutDirection() == YogaDirection.RTL;
|
||||
switch (mAlignment) {
|
||||
// Layout.Alignment.RIGHT and Layout.Alignment.LEFT are @hide :(
|
||||
case Gravity.LEFT:
|
||||
int index = isRtl ? ALIGNMENT_RIGHT : ALIGNMENT_LEFT;
|
||||
return Layout.Alignment.values()[index];
|
||||
case Gravity.RIGHT:
|
||||
index = isRtl ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT;
|
||||
return Layout.Alignment.values()[index];
|
||||
case Gravity.CENTER:
|
||||
return Layout.Alignment.ALIGN_CENTER;
|
||||
case Gravity.NO_GRAVITY:
|
||||
default:
|
||||
return Layout.Alignment.ALIGN_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
private static Layout createTextLayout(
|
||||
int width,
|
||||
YogaMeasureMode widthMode,
|
||||
TextUtils.TruncateAt ellipsize,
|
||||
boolean shouldIncludeFontPadding,
|
||||
int maxLines,
|
||||
boolean isSingleLine,
|
||||
CharSequence text,
|
||||
int textSize,
|
||||
float extraSpacing,
|
||||
float spacingMultiplier,
|
||||
int textStyle,
|
||||
Layout.Alignment textAlignment) {
|
||||
Layout newLayout;
|
||||
|
||||
final @TextLayoutBuilder.MeasureMode int textMeasureMode;
|
||||
switch (widthMode) {
|
||||
case UNDEFINED:
|
||||
textMeasureMode = TextLayoutBuilder.MEASURE_MODE_UNSPECIFIED;
|
||||
break;
|
||||
case EXACTLY:
|
||||
textMeasureMode = TextLayoutBuilder.MEASURE_MODE_EXACTLY;
|
||||
break;
|
||||
case AT_MOST:
|
||||
textMeasureMode = TextLayoutBuilder.MEASURE_MODE_AT_MOST;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected size mode: " + widthMode);
|
||||
}
|
||||
|
||||
sTextLayoutBuilder
|
||||
.setEllipsize(ellipsize)
|
||||
.setMaxLines(maxLines)
|
||||
.setSingleLine(isSingleLine)
|
||||
.setText(text)
|
||||
.setTextSize(textSize)
|
||||
.setWidth(width, textMeasureMode);
|
||||
|
||||
sTextLayoutBuilder.setTextStyle(textStyle);
|
||||
|
||||
sTextLayoutBuilder.setTextDirection(TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR);
|
||||
sTextLayoutBuilder.setIncludeFontPadding(shouldIncludeFontPadding);
|
||||
sTextLayoutBuilder.setTextSpacingExtra(extraSpacing);
|
||||
sTextLayoutBuilder.setTextSpacingMultiplier(spacingMultiplier);
|
||||
sTextLayoutBuilder.setAlignment(textAlignment);
|
||||
|
||||
newLayout = sTextLayoutBuilder.build();
|
||||
|
||||
sTextLayoutBuilder.setText(null);
|
||||
|
||||
return newLayout;
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.imagehelper.ImageSource;
|
||||
|
||||
/**
|
||||
* RCTTextInlineImage
|
||||
*/
|
||||
/* package */ class RCTTextInlineImage extends FlatTextShadowNode {
|
||||
|
||||
private InlineImageSpanWithPipeline mInlineImageSpan = new InlineImageSpanWithPipeline();
|
||||
|
||||
@Override
|
||||
public void setStyleWidth(float width) {
|
||||
super.setStyleWidth(width);
|
||||
|
||||
if (mInlineImageSpan.getWidth() != width) {
|
||||
getMutableSpan().setWidth(width);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStyleHeight(float height) {
|
||||
super.setStyleHeight(height);
|
||||
|
||||
if (mInlineImageSpan.getHeight() != height) {
|
||||
getMutableSpan().setHeight(height);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectText(SpannableStringBuilder builder) {
|
||||
builder.append("I");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performApplySpans(
|
||||
SpannableStringBuilder builder,
|
||||
int begin,
|
||||
int end,
|
||||
boolean isEditable) {
|
||||
mInlineImageSpan.freeze();
|
||||
builder.setSpan(
|
||||
mInlineImageSpan,
|
||||
begin,
|
||||
end,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||
// mInlineImageSpan should already be frozen so no need to freeze it again
|
||||
stateBuilder.addAttachDetachListener(mInlineImageSpan);
|
||||
}
|
||||
|
||||
@ReactProp(name = "src")
|
||||
public void setSource(@Nullable ReadableArray sources) {
|
||||
final String source =
|
||||
(sources == null || sources.size() == 0) ? null : sources.getMap(0).getString("uri");
|
||||
final ImageSource imageSource = source == null ? null :
|
||||
new ImageSource(getThemedContext(), source);
|
||||
getMutableSpan().setImageRequest(imageSource == null ? null :
|
||||
ImageRequestBuilder.newBuilderWithSource(imageSource.getUri()).build());
|
||||
}
|
||||
|
||||
private InlineImageSpanWithPipeline getMutableSpan() {
|
||||
if (mInlineImageSpan.isFrozen()) {
|
||||
mInlineImageSpan = mInlineImageSpan.mutableCopy();
|
||||
}
|
||||
return mInlineImageSpan;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
/**
|
||||
* ViewManager that creates instances of RCTTextInlineImage.
|
||||
*/
|
||||
public final class RCTTextInlineImageManager extends VirtualViewManager<RCTTextInlineImage> {
|
||||
|
||||
/* package */ static final String REACT_CLASS = "RCTTextInlineImage";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTTextInlineImage createShadowNodeInstance() {
|
||||
return new RCTTextInlineImage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTTextInlineImage> getShadowNodeClass() {
|
||||
return RCTTextInlineImage.class;
|
||||
}
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import static com.facebook.react.views.text.ReactRawTextShadowNode.PROP_TEXT;
|
||||
import static com.facebook.react.views.text.ReactTextShadowNode.UNSET;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.text.ReactTextUpdate;
|
||||
import com.facebook.react.views.view.MeasureUtil;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class RCTTextInput extends RCTVirtualText implements AndroidView, YogaMeasureFunction {
|
||||
|
||||
@Nullable private String mText;
|
||||
private int mJsEventCount = UNSET;
|
||||
private boolean mPaddingChanged = false;
|
||||
private int mNumberOfLines = UNSET;
|
||||
private @Nullable EditText mEditText;
|
||||
|
||||
public RCTTextInput() {
|
||||
forceMountToView();
|
||||
setMeasureFunction(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyChanged(boolean shouldRemeasure) {
|
||||
super.notifyChanged(shouldRemeasure);
|
||||
// needed to trigger onCollectExtraUpdates
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
@Override
|
||||
public void setThemedContext(ThemedReactContext themedContext) {
|
||||
super.setThemedContext(themedContext);
|
||||
|
||||
mEditText = new EditText(themedContext);
|
||||
// This is needed to fix an android bug since 4.4.3 which will throw an NPE in measure,
|
||||
// setting the layoutParams fixes it: https://code.google.com/p/android/issues/detail?id=75877
|
||||
mEditText.setLayoutParams(
|
||||
new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
setDefaultPadding(Spacing.START, mEditText.getPaddingStart());
|
||||
setDefaultPadding(Spacing.TOP, mEditText.getPaddingTop());
|
||||
setDefaultPadding(Spacing.END, mEditText.getPaddingEnd());
|
||||
setDefaultPadding(Spacing.BOTTOM, mEditText.getPaddingBottom());
|
||||
mEditText.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long measure(
|
||||
YogaNode node,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode) {
|
||||
// measure() should never be called before setThemedContext()
|
||||
EditText editText = Assertions.assertNotNull(mEditText);
|
||||
|
||||
int fontSize = getFontSize();
|
||||
editText.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX,
|
||||
fontSize == UNSET ?
|
||||
(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) : fontSize);
|
||||
|
||||
if (mNumberOfLines != UNSET) {
|
||||
editText.setLines(mNumberOfLines);
|
||||
}
|
||||
|
||||
editText.measure(
|
||||
MeasureUtil.getMeasureSpec(width, widthMode),
|
||||
MeasureUtil.getMeasureSpec(height, heightMode));
|
||||
return YogaMeasureOutput.make(editText.getMeasuredWidth(), editText.getMeasuredHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtualAnchor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
// suppress, this is handled by a ViewManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
|
||||
super.onCollectExtraUpdates(uiViewOperationQueue);
|
||||
if (mJsEventCount != UNSET) {
|
||||
ReactTextUpdate reactTextUpdate =
|
||||
new ReactTextUpdate(
|
||||
getText(),
|
||||
mJsEventCount,
|
||||
false,
|
||||
getPadding(Spacing.START),
|
||||
getPadding(Spacing.TOP),
|
||||
getPadding(Spacing.END),
|
||||
getPadding(Spacing.BOTTOM),
|
||||
UNSET);
|
||||
// TODO: the Float.NaN should be replaced with the real line height see D3592781
|
||||
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "mostRecentEventCount")
|
||||
public void setMostRecentEventCount(int mostRecentEventCount) {
|
||||
mJsEventCount = mostRecentEventCount;
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = Integer.MAX_VALUE)
|
||||
public void setNumberOfLines(int numberOfLines) {
|
||||
mNumberOfLines = numberOfLines;
|
||||
notifyChanged(true);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_TEXT)
|
||||
public void setText(@Nullable String text) {
|
||||
mText = text;
|
||||
notifyChanged(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int spacingType, float padding) {
|
||||
super.setPadding(spacingType, padding);
|
||||
mPaddingChanged = true;
|
||||
dirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaddingChanged() {
|
||||
return mPaddingChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPaddingChanged() {
|
||||
mPaddingChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldAllowEmptySpans() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectText(SpannableStringBuilder builder) {
|
||||
if (mText != null) {
|
||||
builder.append(mText);
|
||||
}
|
||||
super.performCollectText(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsCustomLayoutForChildren() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import com.facebook.react.views.textinput.ReactTextInputManager;
|
||||
|
||||
public class RCTTextInputManager extends ReactTextInputManager {
|
||||
|
||||
/* package */ static final String REACT_CLASS = ReactTextInputManager.REACT_CLASS;
|
||||
|
||||
@Override
|
||||
public RCTTextInput createShadowNodeInstance() {
|
||||
return new RCTTextInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTTextInput> getShadowNodeClass() {
|
||||
return RCTTextInput.class;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
/**
|
||||
* ViewManager that creates instances of RCTText.
|
||||
*/
|
||||
public final class RCTTextManager extends FlatViewManager {
|
||||
|
||||
/* package */ static final String REACT_CLASS = "RCTText";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTText createShadowNodeInstance() {
|
||||
return new RCTText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTText> getShadowNodeClass() {
|
||||
return RCTText.class;
|
||||
}
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
||||
|
||||
/**
|
||||
* Node for a react View.
|
||||
*/
|
||||
/* package */ final class RCTView extends FlatShadowNode {
|
||||
private static final int[] SPACING_TYPES = {
|
||||
Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM,
|
||||
};
|
||||
|
||||
private @Nullable DrawBorder mDrawBorder;
|
||||
|
||||
boolean mRemoveClippedSubviews;
|
||||
boolean mHorizontal;
|
||||
|
||||
private @Nullable Rect mHitSlop;
|
||||
|
||||
@Override
|
||||
/* package */ void handleUpdateProperties(ReactStylesDiffMap styles) {
|
||||
mRemoveClippedSubviews = mRemoveClippedSubviews ||
|
||||
(styles.hasKey(PROP_REMOVE_CLIPPED_SUBVIEWS) &&
|
||||
styles.getBoolean(PROP_REMOVE_CLIPPED_SUBVIEWS, false));
|
||||
|
||||
if (mRemoveClippedSubviews) {
|
||||
mHorizontal = mHorizontal ||
|
||||
(styles.hasKey(PROP_HORIZONTAL) && styles.getBoolean(PROP_HORIZONTAL, false));
|
||||
}
|
||||
|
||||
super.handleUpdateProperties(styles);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void collectState(
|
||||
StateBuilder stateBuilder,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
super.collectState(
|
||||
stateBuilder,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
|
||||
if (mDrawBorder != null) {
|
||||
mDrawBorder = (DrawBorder) mDrawBorder.updateBoundsAndFreeze(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
stateBuilder.addDrawCommand(mDrawBorder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean doesDraw() {
|
||||
return mDrawBorder != null || super.doesDraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
getMutableBorder().setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderWidths(int index, float borderWidth) {
|
||||
super.setBorderWidths(index, borderWidth);
|
||||
|
||||
int type = SPACING_TYPES[index];
|
||||
getMutableBorder().setBorderWidth(type, PixelUtil.toPixelFromDIP(borderWidth));
|
||||
}
|
||||
|
||||
@ReactProp(name = "nativeBackgroundAndroid")
|
||||
public void setHotspot(@Nullable ReadableMap bg) {
|
||||
if (bg != null) {
|
||||
forceMountToView();
|
||||
}
|
||||
}
|
||||
|
||||
@ReactPropGroup(names = {
|
||||
"borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"
|
||||
}, customType = "Color", defaultDouble = Double.NaN)
|
||||
public void setBorderColor(int index, double color) {
|
||||
int type = SPACING_TYPES[index];
|
||||
if (Double.isNaN(color)) {
|
||||
getMutableBorder().resetBorderColor(type);
|
||||
} else {
|
||||
getMutableBorder().setBorderColor(type, (int) color);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "borderRadius")
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
mClipRadius = borderRadius;
|
||||
if (mClipToBounds && borderRadius > DrawView.MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
// mount to a view if we are overflow: hidden and are clipping, so that we can do one
|
||||
// clipPath to clip all the children of this node (both DrawCommands and Views).
|
||||
forceMountToView();
|
||||
}
|
||||
getMutableBorder().setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
|
||||
}
|
||||
|
||||
@ReactProp(name = "borderStyle")
|
||||
public void setBorderStyle(@Nullable String borderStyle) {
|
||||
getMutableBorder().setBorderStyle(borderStyle);
|
||||
}
|
||||
|
||||
@ReactProp(name = "hitSlop")
|
||||
public void setHitSlop(@Nullable ReadableMap hitSlop) {
|
||||
if (hitSlop == null) {
|
||||
mHitSlop = null;
|
||||
} else {
|
||||
mHitSlop = new Rect(
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")),
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")),
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")),
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")));
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "pointerEvents")
|
||||
public void setPointerEvents(@Nullable String pointerEventsStr) {
|
||||
forceMountToView();
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ void updateNodeRegion(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
boolean isVirtual) {
|
||||
if (!getNodeRegion().matches(left, top, right, bottom, isVirtual)) {
|
||||
setNodeRegion(mHitSlop == null ?
|
||||
new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual) :
|
||||
new HitSlopNodeRegion(mHitSlop, left, top, right, bottom, getReactTag(), isVirtual));
|
||||
}
|
||||
}
|
||||
|
||||
private DrawBorder getMutableBorder() {
|
||||
if (mDrawBorder == null) {
|
||||
mDrawBorder = new DrawBorder();
|
||||
} else if (mDrawBorder.isFrozen()) {
|
||||
mDrawBorder = (DrawBorder) mDrawBorder.mutableCopy();
|
||||
}
|
||||
invalidate();
|
||||
return mDrawBorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clipsSubviews() {
|
||||
return mRemoveClippedSubviews;
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.PointerEvents;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.view.ReactDrawableHelper;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ViewManager that creates instances of RCTView.
|
||||
*/
|
||||
public final class RCTViewManager extends FlatViewManager {
|
||||
|
||||
/* package */ static final String REACT_CLASS = ViewProps.VIEW_CLASS_NAME;
|
||||
|
||||
private static final int[] TMP_INT_ARRAY = new int[2];
|
||||
|
||||
private static final int CMD_HOTSPOT_UPDATE = 1;
|
||||
private static final int CMD_SET_PRESSED = 2;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getCommandsMap() {
|
||||
return MapBuilder.of("hotspotUpdate", CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTView createShadowNodeInstance() {
|
||||
return new RCTView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTView> getShadowNodeClass() {
|
||||
return RCTView.class;
|
||||
}
|
||||
|
||||
@ReactProp(name = "nativeBackgroundAndroid")
|
||||
public void setHotspot(FlatViewGroup view, @Nullable ReadableMap bg) {
|
||||
view.setHotspot(bg == null ?
|
||||
null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveCommand(
|
||||
FlatViewGroup view,
|
||||
int commandId,
|
||||
@Nullable ReadableArray args) {
|
||||
switch (commandId) {
|
||||
case CMD_HOTSPOT_UPDATE: {
|
||||
if (args == null || args.size() != 2) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Illegal number of arguments for 'updateHotspot' command");
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
view.getLocationOnScreen(TMP_INT_ARRAY);
|
||||
float x = PixelUtil.toPixelFromDIP(args.getDouble(0)) - TMP_INT_ARRAY[0];
|
||||
float y = PixelUtil.toPixelFromDIP(args.getDouble(1)) - TMP_INT_ARRAY[1];
|
||||
view.drawableHotspotChanged(x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMD_SET_PRESSED: {
|
||||
if (args == null || args.size() != 1) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Illegal number of arguments for 'setPressed' command");
|
||||
}
|
||||
view.setPressed(args.getBoolean(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
|
||||
public void setNeedsOffscreenAlphaCompositing(
|
||||
FlatViewGroup view,
|
||||
boolean needsOffscreenAlphaCompositing) {
|
||||
view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing);
|
||||
}
|
||||
|
||||
@ReactProp(name = "pointerEvents")
|
||||
public void setPointerEvents(FlatViewGroup view, @Nullable String pointerEventsStr) {
|
||||
view.setPointerEvents(parsePointerEvents(pointerEventsStr));
|
||||
}
|
||||
|
||||
@ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
|
||||
public void setRemoveClippedSubviews(FlatViewGroup view, boolean removeClippedSubviews) {
|
||||
view.setRemoveClippedSubviews(removeClippedSubviews);
|
||||
}
|
||||
|
||||
private static PointerEvents parsePointerEvents(@Nullable String pointerEventsStr) {
|
||||
if (pointerEventsStr != null) {
|
||||
switch (pointerEventsStr) {
|
||||
case "none":
|
||||
return PointerEvents.NONE;
|
||||
case "auto":
|
||||
return PointerEvents.AUTO;
|
||||
case "box-none":
|
||||
return PointerEvents.BOX_NONE;
|
||||
case "box-only":
|
||||
return PointerEvents.BOX_ONLY;
|
||||
}
|
||||
}
|
||||
// default or invalid
|
||||
return PointerEvents.AUTO;
|
||||
}
|
||||
|
||||
@ReactProp(name = "hitSlop")
|
||||
public void setHitSlop(FlatViewGroup view, @Nullable ReadableMap hitSlop) {
|
||||
if (hitSlop == null) {
|
||||
view.setHitSlopRect(null);
|
||||
} else {
|
||||
view.setHitSlopRect(new Rect(
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")),
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")),
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")),
|
||||
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom"))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.view.View;
|
||||
import com.facebook.react.views.viewpager.ReactViewPager;
|
||||
import com.facebook.react.views.viewpager.ReactViewPagerManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RCTViewPagerManager extends ReactViewPagerManager {
|
||||
|
||||
/* package */ static final String REACT_CLASS = ReactViewPagerManager.REACT_CLASS;
|
||||
|
||||
@Override
|
||||
public void addViews(ReactViewPager parent, List<View> views) {
|
||||
parent.setViews(views);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllViews(ReactViewPager parent) {
|
||||
parent.removeAllViewsFromAdapter();
|
||||
}
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* RCTVirtualText is a {@link FlatTextShadowNode} that can contain font styling information.
|
||||
*/
|
||||
/* package */ class RCTVirtualText extends FlatTextShadowNode {
|
||||
|
||||
private static final String BOLD = "bold";
|
||||
private static final String ITALIC = "italic";
|
||||
private static final String NORMAL = "normal";
|
||||
|
||||
private static final String PROP_SHADOW_OFFSET = "textShadowOffset";
|
||||
private static final String PROP_SHADOW_RADIUS = "textShadowRadius";
|
||||
private static final String PROP_SHADOW_COLOR = "textShadowColor";
|
||||
private static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000;
|
||||
|
||||
private FontStylingSpan mFontStylingSpan = FontStylingSpan.INSTANCE;
|
||||
private ShadowStyleSpan mShadowStyleSpan = ShadowStyleSpan.INSTANCE;
|
||||
|
||||
@Override
|
||||
public void addChildAt(ReactShadowNodeImpl child, int i) {
|
||||
super.addChildAt(child, i);
|
||||
notifyChanged(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectText(SpannableStringBuilder builder) {
|
||||
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
|
||||
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
|
||||
child.collectText(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performApplySpans(SpannableStringBuilder builder, int begin, int end, boolean isEditable) {
|
||||
mFontStylingSpan.freeze();
|
||||
|
||||
// All spans will automatically extend to the right of the text, but not the left - except
|
||||
// for spans that start at the beginning of the text.
|
||||
final int flag;
|
||||
if (isEditable) {
|
||||
flag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
|
||||
} else {
|
||||
flag = begin == 0 ?
|
||||
Spannable.SPAN_INCLUSIVE_INCLUSIVE :
|
||||
Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
|
||||
}
|
||||
|
||||
builder.setSpan(
|
||||
mFontStylingSpan,
|
||||
begin,
|
||||
end,
|
||||
flag);
|
||||
|
||||
if (mShadowStyleSpan.getColor() != 0 && mShadowStyleSpan.getRadius() != 0) {
|
||||
mShadowStyleSpan.freeze();
|
||||
|
||||
builder.setSpan(
|
||||
mShadowStyleSpan,
|
||||
begin,
|
||||
end,
|
||||
flag);
|
||||
}
|
||||
|
||||
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
|
||||
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
|
||||
child.applySpans(builder, isEditable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
|
||||
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
|
||||
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
|
||||
child.performCollectAttachDetachListeners(stateBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_SIZE, defaultFloat = Float.NaN)
|
||||
public void setFontSize(float fontSizeSp) {
|
||||
final int fontSize;
|
||||
if (Float.isNaN(fontSizeSp)) {
|
||||
fontSize = getDefaultFontSize();
|
||||
} else {
|
||||
fontSize = fontSizeFromSp(fontSizeSp);
|
||||
}
|
||||
|
||||
if (mFontStylingSpan.getFontSize() != fontSize) {
|
||||
getSpan().setFontSize(fontSize);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.COLOR, defaultDouble = Double.NaN)
|
||||
public void setColor(double textColor) {
|
||||
if (mFontStylingSpan.getTextColor() != textColor) {
|
||||
getSpan().setTextColor(textColor);
|
||||
notifyChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
if (isVirtual()) {
|
||||
// for nested Text elements, we want to apply background color to the text only
|
||||
// e.g. Hello <style backgroundColor=red>World</style>, "World" will have red background color
|
||||
if (mFontStylingSpan.getBackgroundColor() != backgroundColor) {
|
||||
getSpan().setBackgroundColor(backgroundColor);
|
||||
notifyChanged(false);
|
||||
}
|
||||
} else {
|
||||
// for top-level Text element, background needs to be applied for the entire shadow node
|
||||
//
|
||||
// For example: <Text style={flex:1}>Hello World</Text>
|
||||
// "Hello World" may only occupy e.g. 200 pixels, but the node may be measured at e.g. 500px.
|
||||
// In this case, we want background to be 500px wide as well, and this is exactly what
|
||||
// FlatShadowNode does.
|
||||
super.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_FAMILY)
|
||||
public void setFontFamily(@Nullable String fontFamily) {
|
||||
if (!TextUtils.equals(mFontStylingSpan.getFontFamily(), fontFamily)) {
|
||||
getSpan().setFontFamily(fontFamily);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_WEIGHT)
|
||||
public void setFontWeight(@Nullable String fontWeightString) {
|
||||
final int fontWeight;
|
||||
if (fontWeightString == null) {
|
||||
fontWeight = -1;
|
||||
} else if (BOLD.equals(fontWeightString)) {
|
||||
fontWeight = Typeface.BOLD;
|
||||
} else if (NORMAL.equals(fontWeightString)) {
|
||||
fontWeight = Typeface.NORMAL;
|
||||
} else {
|
||||
int fontWeightNumeric = parseNumericFontWeight(fontWeightString);
|
||||
if (fontWeightNumeric == -1) {
|
||||
throw new RuntimeException("invalid font weight " + fontWeightString);
|
||||
}
|
||||
fontWeight = fontWeightNumeric >= 500 ? Typeface.BOLD : Typeface.NORMAL;
|
||||
}
|
||||
|
||||
if (mFontStylingSpan.getFontWeight() != fontWeight) {
|
||||
getSpan().setFontWeight(fontWeight);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.TEXT_DECORATION_LINE)
|
||||
public void setTextDecorationLine(@Nullable String textDecorationLineString) {
|
||||
boolean isUnderlineTextDecorationSet = false;
|
||||
boolean isLineThroughTextDecorationSet = false;
|
||||
if (textDecorationLineString != null) {
|
||||
for (String textDecorationLineSubString : textDecorationLineString.split(" ")) {
|
||||
if ("underline".equals(textDecorationLineSubString)) {
|
||||
isUnderlineTextDecorationSet = true;
|
||||
} else if ("line-through".equals(textDecorationLineSubString)) {
|
||||
isLineThroughTextDecorationSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnderlineTextDecorationSet != mFontStylingSpan.hasUnderline() ||
|
||||
isLineThroughTextDecorationSet != mFontStylingSpan.hasStrikeThrough()) {
|
||||
FontStylingSpan span = getSpan();
|
||||
span.setHasUnderline(isUnderlineTextDecorationSet);
|
||||
span.setHasStrikeThrough(isLineThroughTextDecorationSet);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.FONT_STYLE)
|
||||
public void setFontStyle(@Nullable String fontStyleString) {
|
||||
final int fontStyle;
|
||||
if (fontStyleString == null) {
|
||||
fontStyle = -1;
|
||||
} else if (ITALIC.equals(fontStyleString)) {
|
||||
fontStyle = Typeface.ITALIC;
|
||||
} else if (NORMAL.equals(fontStyleString)) {
|
||||
fontStyle = Typeface.NORMAL;
|
||||
} else {
|
||||
throw new RuntimeException("invalid font style " + fontStyleString);
|
||||
}
|
||||
|
||||
if (mFontStylingSpan.getFontStyle() != fontStyle) {
|
||||
getSpan().setFontStyle(fontStyle);
|
||||
notifyChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHADOW_OFFSET)
|
||||
public void setTextShadowOffset(@Nullable ReadableMap offsetMap) {
|
||||
float dx = 0;
|
||||
float dy = 0;
|
||||
if (offsetMap != null) {
|
||||
if (offsetMap.hasKey("width")) {
|
||||
dx = PixelUtil.toPixelFromDIP(offsetMap.getDouble("width"));
|
||||
}
|
||||
if (offsetMap.hasKey("height")) {
|
||||
dy = PixelUtil.toPixelFromDIP(offsetMap.getDouble("height"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!mShadowStyleSpan.offsetMatches(dx, dy)) {
|
||||
getShadowSpan().setOffset(dx, dy);
|
||||
notifyChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHADOW_RADIUS)
|
||||
public void setTextShadowRadius(float textShadowRadius) {
|
||||
textShadowRadius = PixelUtil.toPixelFromDIP(textShadowRadius);
|
||||
if (mShadowStyleSpan.getRadius() != textShadowRadius) {
|
||||
getShadowSpan().setRadius(textShadowRadius);
|
||||
notifyChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color")
|
||||
public void setTextShadowColor(int textShadowColor) {
|
||||
if (mShadowStyleSpan.getColor() != textShadowColor) {
|
||||
getShadowSpan().setColor(textShadowColor);
|
||||
notifyChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns font size for this node.
|
||||
* When called on RCTText, this value is never -1 (unset).
|
||||
*/
|
||||
protected final int getFontSize() {
|
||||
return mFontStylingSpan.getFontSize();
|
||||
}
|
||||
/**
|
||||
* Returns font style for this node.
|
||||
*/
|
||||
protected final int getFontStyle() {
|
||||
int style = mFontStylingSpan.getFontStyle();
|
||||
return style >= 0 ? style : Typeface.NORMAL;
|
||||
}
|
||||
|
||||
protected int getDefaultFontSize() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* package */ static int fontSizeFromSp(float sp) {
|
||||
return (int) Math.ceil(PixelUtil.toPixelFromSP(sp));
|
||||
}
|
||||
|
||||
protected final FontStylingSpan getSpan() {
|
||||
if (mFontStylingSpan.isFrozen()) {
|
||||
mFontStylingSpan = mFontStylingSpan.mutableCopy();
|
||||
}
|
||||
return mFontStylingSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new SpannableStringBuilder that includes all the text and styling information to
|
||||
* create the Layout.
|
||||
*/
|
||||
/* package */ final SpannableStringBuilder getText() {
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
collectText(sb);
|
||||
applySpans(sb, isEditable());
|
||||
return sb;
|
||||
}
|
||||
|
||||
private final ShadowStyleSpan getShadowSpan() {
|
||||
if (mShadowStyleSpan.isFrozen()) {
|
||||
mShadowStyleSpan = mShadowStyleSpan.mutableCopy();
|
||||
}
|
||||
return mShadowStyleSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
|
||||
* return the weight.
|
||||
*/
|
||||
private static int parseNumericFontWeight(String fontWeightString) {
|
||||
// This should be much faster than using regex to verify input and Integer.parseInt
|
||||
return fontWeightString.length() == 3 && fontWeightString.endsWith("00")
|
||||
&& fontWeightString.charAt(0) <= '9' && fontWeightString.charAt(0) >= '1' ?
|
||||
100 * (fontWeightString.charAt(0) - '0') : -1;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
/**
|
||||
* ViewManager that creates instances of RCTVirtualText.
|
||||
*/
|
||||
public final class RCTVirtualTextManager extends VirtualViewManager<RCTVirtualText> {
|
||||
|
||||
/* package */ static final String REACT_CLASS = "RCTVirtualText";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RCTVirtualText createShadowNodeInstance() {
|
||||
return new RCTVirtualText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RCTVirtualText> getShadowNodeClass() {
|
||||
return RCTVirtualText.class;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
# Nodes
|
||||
|
||||
Nodes is an experimental, alternate version of
|
||||
[UIImplementation](https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java) for ReactNative on Android. It has two main advantages over the existing `UIImplementation`:
|
||||
|
||||
1. Support for `overflow:visible` on Android.
|
||||
2. More efficient generation of view hierarchies.
|
||||
|
||||
The intention is to ultimately replace the existing `UIImplementation` on
|
||||
Android with Nodes (after all the issues are ironed out).
|
||||
|
||||
## How to test
|
||||
|
||||
In a subclass of `ReactNativeHost`, add this:
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected UIImplementationProvider getUIImplementationProvider() {
|
||||
return new FlatUIImplementationProvider();
|
||||
}
|
||||
```
|
||||
|
||||
## How it Works
|
||||
|
||||
The existing
|
||||
[UIImplementation](https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java) maps all non-layout tags to `View`s (resulting in an almost 1:1 mapping of tags
|
||||
to Views, with the exception of some optimizations for layout only tags that
|
||||
don't draw content). Nodes, on the other hand, maps react tags to a set of
|
||||
`DrawCommand`s. In other words, an `<image>` tag will often be mapped to a
|
||||
`Drawable` instead of an `ImageView` and a `<text>` tag will be mapped to a
|
||||
`Layout` instead of a `TextView`. This helps flatten the resulting `View`
|
||||
hierarchy.
|
||||
|
||||
There are situations where `DrawCommand`s are promoted to `View`s:
|
||||
|
||||
1. Existing Android components that are wrapped by React Native (for example,
|
||||
`ViewPager`, `ScrollView`, etc).
|
||||
2. When using a `View` is more optimal (for example, `opacity`, to avoid
|
||||
unnecessary invalidations).
|
||||
3. To facilitate the implementation of certain features (accessibility,
|
||||
transforms, etc).
|
||||
|
||||
This means that existing custom `ViewManager`s should continue to work as they
|
||||
did with the existing `UIImplementation`.
|
||||
|
||||
## Limitations and Known Issues
|
||||
|
||||
- `LayoutAnimation`s are not yet supported
|
||||
- `zIndex` is not yet supported
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.CharacterStyle;
|
||||
|
||||
/* package */ final class ShadowStyleSpan extends CharacterStyle {
|
||||
|
||||
/* package */ static final ShadowStyleSpan INSTANCE = new ShadowStyleSpan(0, 0, 0, 0, true);
|
||||
|
||||
private float mDx;
|
||||
private float mDy;
|
||||
private float mRadius;
|
||||
private int mColor;
|
||||
private boolean mFrozen;
|
||||
|
||||
private ShadowStyleSpan(float dx, float dy, float radius, int color, boolean frozen) {
|
||||
mDx = dx;
|
||||
mDy = dy;
|
||||
mRadius = radius;
|
||||
mColor = color;
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
public boolean offsetMatches(float dx, float dy) {
|
||||
return mDx == dx && mDy == dy;
|
||||
}
|
||||
|
||||
public void setOffset(float dx, float dy) {
|
||||
mDx = dx;
|
||||
mDy = dy;
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return mRadius;
|
||||
}
|
||||
|
||||
public void setRadius(float radius) {
|
||||
mRadius = radius;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
/* package */ ShadowStyleSpan mutableCopy() {
|
||||
return new ShadowStyleSpan(mDx, mDy, mRadius, mColor, false);
|
||||
}
|
||||
|
||||
/* package */ boolean isFrozen() {
|
||||
return mFrozen;
|
||||
}
|
||||
|
||||
/* package */ void freeze() {
|
||||
mFrozen = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint textPaint) {
|
||||
textPaint.setShadowLayer(mRadius, mDx, mDy, mColor);
|
||||
}
|
||||
}
|
|
@ -1,729 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.OnLayoutEvent;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
/**
|
||||
* Shadow node hierarchy by itself cannot display UI, it is only a representation of what UI should
|
||||
* be from JavaScript perspective. StateBuilder is a helper class that walks the shadow node tree
|
||||
* and collects information into an operation queue that is run on the UI thread and applied to the
|
||||
* non-shadow hierarchy of Views that Android can finally display.
|
||||
*/
|
||||
/* package */ final class StateBuilder {
|
||||
/* package */ static final float[] EMPTY_FLOAT_ARRAY = new float[0];
|
||||
/* package */ static final SparseIntArray EMPTY_SPARSE_INT = new SparseIntArray();
|
||||
|
||||
private static final boolean SKIP_UP_TO_DATE_NODES = true;
|
||||
|
||||
// Optimization to avoid re-allocating zero length arrays.
|
||||
private static final int[] EMPTY_INT_ARRAY = new int[0];
|
||||
|
||||
private final FlatUIViewOperationQueue mOperationsQueue;
|
||||
|
||||
private final ElementsList<DrawCommand> mDrawCommands =
|
||||
new ElementsList<>(DrawCommand.EMPTY_ARRAY);
|
||||
private final ElementsList<AttachDetachListener> mAttachDetachListeners =
|
||||
new ElementsList<>(AttachDetachListener.EMPTY_ARRAY);
|
||||
private final ElementsList<NodeRegion> mNodeRegions =
|
||||
new ElementsList<>(NodeRegion.EMPTY_ARRAY);
|
||||
private final ElementsList<FlatShadowNode> mNativeChildren =
|
||||
new ElementsList<>(FlatShadowNode.EMPTY_ARRAY);
|
||||
|
||||
private final ArrayList<FlatShadowNode> mViewsToDetachAllChildrenFrom = new ArrayList<>();
|
||||
private final ArrayList<FlatShadowNode> mViewsToDetach = new ArrayList<>();
|
||||
private final ArrayList<Integer> mViewsToDrop = new ArrayList<>();
|
||||
private final ArrayList<Integer> mParentsForViewsToDrop = new ArrayList<>();
|
||||
private final ArrayList<OnLayoutEvent> mOnLayoutEvents = new ArrayList<>();
|
||||
private final ArrayList<UIViewOperationQueue.UIOperation> mUpdateViewBoundsOperations =
|
||||
new ArrayList<>();
|
||||
private final ArrayList<UIViewOperationQueue.UIOperation> mViewManagerCommands =
|
||||
new ArrayList<>();
|
||||
|
||||
private @Nullable FlatUIViewOperationQueue.DetachAllChildrenFromViews mDetachAllChildrenFromViews;
|
||||
|
||||
/* package */ StateBuilder(FlatUIViewOperationQueue operationsQueue) {
|
||||
mOperationsQueue = operationsQueue;
|
||||
}
|
||||
|
||||
/* package */ FlatUIViewOperationQueue getOperationsQueue() {
|
||||
return mOperationsQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a root of the laid-out shadow node hierarchy, walks the tree and generates arrays from
|
||||
* element lists that are mounted in the UI thread to FlatViewGroups to handle drawing, touch,
|
||||
* and other logic.
|
||||
*/
|
||||
/* package */ void applyUpdates(FlatShadowNode node) {
|
||||
float width = node.getLayoutWidth();
|
||||
float height = node.getLayoutHeight();
|
||||
float left = node.getLayoutX();
|
||||
float top = node.getLayoutY();
|
||||
float right = left + width;
|
||||
float bottom = top + height;
|
||||
|
||||
collectStateForMountableNode(
|
||||
node,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
Float.NEGATIVE_INFINITY,
|
||||
Float.NEGATIVE_INFINITY,
|
||||
Float.POSITIVE_INFINITY,
|
||||
Float.POSITIVE_INFINITY);
|
||||
|
||||
updateViewBounds(node, left, top, right, bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run after the shadow node hierarchy is updated. Detaches all children from Views that are
|
||||
* changing their native children, updates views, and dispatches commands before discarding any
|
||||
* dropped views.
|
||||
*
|
||||
* @param eventDispatcher Dispatcher for onLayout events.
|
||||
*/
|
||||
void afterUpdateViewHierarchy(EventDispatcher eventDispatcher) {
|
||||
if (mDetachAllChildrenFromViews != null) {
|
||||
int[] viewsToDetachAllChildrenFrom = collectViewTags(mViewsToDetachAllChildrenFrom);
|
||||
mViewsToDetachAllChildrenFrom.clear();
|
||||
|
||||
mDetachAllChildrenFromViews.setViewsToDetachAllChildrenFrom(viewsToDetachAllChildrenFrom);
|
||||
mDetachAllChildrenFromViews = null;
|
||||
}
|
||||
|
||||
for (int i = 0, size = mUpdateViewBoundsOperations.size(); i != size; ++i) {
|
||||
mOperationsQueue.enqueueFlatUIOperation(mUpdateViewBoundsOperations.get(i));
|
||||
}
|
||||
mUpdateViewBoundsOperations.clear();
|
||||
|
||||
// Process view manager commands after bounds operations, so that any UI operations have already
|
||||
// happened before we actually dispatch the view manager command. This prevents things like
|
||||
// commands going to empty parents and views not yet being created.
|
||||
for (int i = 0, size = mViewManagerCommands.size(); i != size; i++) {
|
||||
mOperationsQueue.enqueueFlatUIOperation(mViewManagerCommands.get(i));
|
||||
}
|
||||
mViewManagerCommands.clear();
|
||||
|
||||
// This could be more efficient if EventDispatcher had a batch mode
|
||||
// to avoid multiple synchronized calls.
|
||||
for (int i = 0, size = mOnLayoutEvents.size(); i != size; ++i) {
|
||||
eventDispatcher.dispatchEvent(mOnLayoutEvents.get(i));
|
||||
}
|
||||
mOnLayoutEvents.clear();
|
||||
|
||||
if (mViewsToDrop.size() > 0) {
|
||||
mOperationsQueue.enqueueDropViews(mViewsToDrop, mParentsForViewsToDrop);
|
||||
mViewsToDrop.clear();
|
||||
mParentsForViewsToDrop.clear();
|
||||
}
|
||||
|
||||
mOperationsQueue.enqueueProcessLayoutRequests();
|
||||
}
|
||||
|
||||
/* package */ void removeRootView(int rootViewTag) {
|
||||
// Note root view tags with a negative value.
|
||||
mViewsToDrop.add(-rootViewTag);
|
||||
mParentsForViewsToDrop.add(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a draw command to the element list for the current scope. Allows collectState within the
|
||||
* shadow node to add commands.
|
||||
*
|
||||
* @param drawCommand The draw command to add.
|
||||
*/
|
||||
/* package */ void addDrawCommand(AbstractDrawCommand drawCommand) {
|
||||
mDrawCommands.add(drawCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to the element list for the current scope. Allows collectState within the
|
||||
* shadow node to add listeners.
|
||||
*
|
||||
* @param listener The listener to add
|
||||
*/
|
||||
/* package */ void addAttachDetachListener(AttachDetachListener listener) {
|
||||
mAttachDetachListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a command for a view manager to the queue. We have to delay adding it to the operations
|
||||
* queue until we have added our view moves, creations and updates.
|
||||
*
|
||||
* @param reactTag The react tag of the command target.
|
||||
* @param commandId ID of the command.
|
||||
* @param commandArgs Arguments for the command.
|
||||
*/
|
||||
/* package */ void enqueueViewManagerCommand(
|
||||
int reactTag,
|
||||
int commandId,
|
||||
ReadableArray commandArgs) {
|
||||
mViewManagerCommands.add(
|
||||
mOperationsQueue.createViewManagerCommand(reactTag, commandId, commandArgs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a backing view for a node, or update the backing view if it has already been created.
|
||||
*
|
||||
* @param node The node to create the backing view for.
|
||||
* @param styles Styles for the view.
|
||||
*/
|
||||
/* package */ void enqueueCreateOrUpdateView(
|
||||
FlatShadowNode node,
|
||||
@Nullable ReactStylesDiffMap styles) {
|
||||
if (node.isBackingViewCreated()) {
|
||||
// If the View is already created, make sure to propagate the new styles.
|
||||
mOperationsQueue.enqueueUpdateProperties(
|
||||
node.getReactTag(),
|
||||
node.getViewClass(),
|
||||
styles);
|
||||
} else {
|
||||
mOperationsQueue.enqueueCreateView(
|
||||
node.getThemedContext(),
|
||||
node.getReactTag(),
|
||||
node.getViewClass(),
|
||||
styles);
|
||||
|
||||
node.signalBackingViewIsCreated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a backing view for a node if not already created.
|
||||
*
|
||||
* @param node The node to create the backing view for.
|
||||
*/
|
||||
/* package */ void ensureBackingViewIsCreated(FlatShadowNode node) {
|
||||
if (node.isBackingViewCreated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tag = node.getReactTag();
|
||||
mOperationsQueue.enqueueCreateView(node.getThemedContext(), tag, node.getViewClass(), null);
|
||||
|
||||
node.signalBackingViewIsCreated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue dropping of the view for a node that has a backing view. Used in conjunction with
|
||||
* remove the node from the shadow hierarchy.
|
||||
*
|
||||
* @param node The node to drop the backing view for.
|
||||
*/
|
||||
/* package */ void dropView(FlatShadowNode node, int parentReactTag) {
|
||||
mViewsToDrop.add(node.getReactTag());
|
||||
mParentsForViewsToDrop.add(parentReactTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node region to the element list for the current scope. Allows collectState to add
|
||||
* regions.
|
||||
*
|
||||
* @param node The node to add a region for.
|
||||
* @param left Bound of the region.
|
||||
* @param top Bound of the region.
|
||||
* @param right Bound of the region.
|
||||
* @param bottom Bound of the region.
|
||||
* @param isVirtual True if the region does not map to a native view. Used to determine touch
|
||||
* targets.
|
||||
*/
|
||||
private void addNodeRegion(
|
||||
FlatShadowNode node,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
boolean isVirtual) {
|
||||
if (left == right || top == bottom) {
|
||||
// no point in adding an empty NodeRegion
|
||||
return;
|
||||
}
|
||||
|
||||
node.updateNodeRegion(left, top, right, bottom, isVirtual);
|
||||
if (node.doesDraw()) {
|
||||
mNodeRegions.add(node.getNodeRegion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a native child to the element list for the current scope. Allows collectState to add
|
||||
* native children.
|
||||
*
|
||||
* @param nativeChild The view-backed native child to add.
|
||||
*/
|
||||
private void addNativeChild(FlatShadowNode nativeChild) {
|
||||
mNativeChildren.add(nativeChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates boundaries of a View that a give nodes maps to.
|
||||
*/
|
||||
private void updateViewBounds(
|
||||
FlatShadowNode node,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom) {
|
||||
int viewLeft = Math.round(left);
|
||||
int viewTop = Math.round(top);
|
||||
int viewRight = Math.round(right);
|
||||
int viewBottom = Math.round(bottom);
|
||||
if (node.getViewLeft() == viewLeft && node.getViewTop() == viewTop &&
|
||||
node.getViewRight() == viewRight && node.getViewBottom() == viewBottom) {
|
||||
// nothing changed.
|
||||
return;
|
||||
}
|
||||
|
||||
// this will optionally measure and layout the View this node maps to.
|
||||
node.setViewBounds(viewLeft, viewTop, viewRight, viewBottom);
|
||||
int tag = node.getReactTag();
|
||||
|
||||
mUpdateViewBoundsOperations.add(
|
||||
mOperationsQueue.createUpdateViewBounds(tag, viewLeft, viewTop, viewRight, viewBottom));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects state (Draw commands, listeners, regions, native children) for a given node that will
|
||||
* mount to a View. Returns true if this node or any of its descendants that mount to View
|
||||
* generated any updates.
|
||||
*/
|
||||
private boolean collectStateForMountableNode(
|
||||
FlatShadowNode node,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
boolean hasUpdates = node.hasNewLayout();
|
||||
|
||||
boolean expectingUpdate = hasUpdates || node.isUpdated() || node.hasUnseenUpdates() ||
|
||||
node.clipBoundsChanged(clipLeft, clipTop, clipRight, clipBottom);
|
||||
if (SKIP_UP_TO_DATE_NODES && !expectingUpdate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
|
||||
|
||||
mDrawCommands.start(node.getDrawCommands());
|
||||
mAttachDetachListeners.start(node.getAttachDetachListeners());
|
||||
mNodeRegions.start(node.getNodeRegions());
|
||||
mNativeChildren.start(node.getNativeChildren());
|
||||
|
||||
boolean isAndroidView = false;
|
||||
boolean needsCustomLayoutForChildren = false;
|
||||
if (node instanceof AndroidView) {
|
||||
AndroidView androidView = (AndroidView) node;
|
||||
updateViewPadding(androidView, node.getReactTag());
|
||||
|
||||
isAndroidView = true;
|
||||
needsCustomLayoutForChildren = androidView.needsCustomLayoutForChildren();
|
||||
|
||||
// AndroidView might scroll (e.g. ScrollView) so we need to reset clip bounds here
|
||||
// Otherwise, we might scroll clipped content. If AndroidView doesn't scroll, this is still
|
||||
// harmless, because AndroidView will do its own clipping anyway.
|
||||
clipLeft = Float.NEGATIVE_INFINITY;
|
||||
clipTop = Float.NEGATIVE_INFINITY;
|
||||
clipRight = Float.POSITIVE_INFINITY;
|
||||
clipBottom = Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
if (!isAndroidView && node.isVirtualAnchor()) {
|
||||
// If RCTText is mounted to View, virtual children will not receive any touch events
|
||||
// because they don't get added to nodeRegions, so nodeRegions will be empty and
|
||||
// FlatViewGroup.reactTagForTouch() will always return RCTText's id. To fix the issue,
|
||||
// manually add nodeRegion so it will have exactly one NodeRegion, and virtual nodes will
|
||||
// be able to receive touch events.
|
||||
addNodeRegion(node, left, top, right, bottom, true);
|
||||
}
|
||||
|
||||
boolean descendantUpdated = collectStateRecursively(
|
||||
node,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom,
|
||||
isAndroidView,
|
||||
needsCustomLayoutForChildren);
|
||||
|
||||
boolean shouldUpdateMountState = false;
|
||||
final DrawCommand[] drawCommands = mDrawCommands.finish();
|
||||
if (drawCommands != null) {
|
||||
shouldUpdateMountState = true;
|
||||
node.setDrawCommands(drawCommands);
|
||||
}
|
||||
|
||||
final AttachDetachListener[] listeners = mAttachDetachListeners.finish();
|
||||
if (listeners != null) {
|
||||
shouldUpdateMountState = true;
|
||||
node.setAttachDetachListeners(listeners);
|
||||
}
|
||||
|
||||
final NodeRegion[] nodeRegions = mNodeRegions.finish();
|
||||
if (nodeRegions != null) {
|
||||
shouldUpdateMountState = true;
|
||||
node.setNodeRegions(nodeRegions);
|
||||
} else if (descendantUpdated) {
|
||||
// one of the descendant's value for overflows container may have changed, so
|
||||
// we still need to update ours.
|
||||
node.updateOverflowsContainer();
|
||||
}
|
||||
|
||||
// We need to finish the native children so that we can process clipping FlatViewGroup.
|
||||
final FlatShadowNode[] nativeChildren = mNativeChildren.finish();
|
||||
if (shouldUpdateMountState) {
|
||||
if (node.clipsSubviews()) {
|
||||
// Node is a clipping FlatViewGroup, so lets do some calculations off the UI thread.
|
||||
// DrawCommandManager has a better explanation of the data incoming from these calculations,
|
||||
// and is where they are actually used.
|
||||
float[] commandMaxBottom = EMPTY_FLOAT_ARRAY;
|
||||
float[] commandMinTop = EMPTY_FLOAT_ARRAY;
|
||||
SparseIntArray drawViewIndexMap = EMPTY_SPARSE_INT;
|
||||
if (drawCommands != null) {
|
||||
drawViewIndexMap = new SparseIntArray();
|
||||
|
||||
commandMaxBottom = new float[drawCommands.length];
|
||||
commandMinTop = new float[drawCommands.length];
|
||||
|
||||
if (node.isHorizontal()) {
|
||||
HorizontalDrawCommandManager
|
||||
.fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
|
||||
} else {
|
||||
VerticalDrawCommandManager
|
||||
.fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
|
||||
}
|
||||
}
|
||||
float[] regionMaxBottom = EMPTY_FLOAT_ARRAY;
|
||||
float[] regionMinTop = EMPTY_FLOAT_ARRAY;
|
||||
if (nodeRegions != null) {
|
||||
regionMaxBottom = new float[nodeRegions.length];
|
||||
regionMinTop = new float[nodeRegions.length];
|
||||
|
||||
if (node.isHorizontal()) {
|
||||
HorizontalDrawCommandManager
|
||||
.fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
|
||||
} else {
|
||||
VerticalDrawCommandManager
|
||||
.fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
|
||||
}
|
||||
}
|
||||
|
||||
boolean willMountViews = nativeChildren != null;
|
||||
mOperationsQueue.enqueueUpdateClippingMountState(
|
||||
node.getReactTag(),
|
||||
drawCommands,
|
||||
drawViewIndexMap,
|
||||
commandMaxBottom,
|
||||
commandMinTop,
|
||||
listeners,
|
||||
nodeRegions,
|
||||
regionMaxBottom,
|
||||
regionMinTop,
|
||||
willMountViews);
|
||||
} else {
|
||||
mOperationsQueue.enqueueUpdateMountState(
|
||||
node.getReactTag(),
|
||||
drawCommands,
|
||||
listeners,
|
||||
nodeRegions);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.hasUnseenUpdates()) {
|
||||
node.onCollectExtraUpdates(mOperationsQueue);
|
||||
node.markUpdateSeen();
|
||||
}
|
||||
|
||||
if (nativeChildren != null) {
|
||||
updateNativeChildren(node, node.getNativeChildren(), nativeChildren);
|
||||
}
|
||||
|
||||
boolean updated = shouldUpdateMountState || nativeChildren != null || descendantUpdated;
|
||||
|
||||
if (!expectingUpdate && updated) {
|
||||
throw new RuntimeException("Node " + node.getReactTag() + " updated unexpectedly.");
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updating the children of a node when they change. Updates the shadow node and
|
||||
* enqueues state updates that will eventually be run on the UI thread.
|
||||
*
|
||||
* @param node The node to update native children for.
|
||||
* @param oldNativeChildren The previously mounted native children.
|
||||
* @param newNativeChildren The newly mounted native children.
|
||||
*/
|
||||
private void updateNativeChildren(
|
||||
FlatShadowNode node,
|
||||
FlatShadowNode[] oldNativeChildren,
|
||||
FlatShadowNode[] newNativeChildren) {
|
||||
|
||||
node.setNativeChildren(newNativeChildren);
|
||||
|
||||
if (mDetachAllChildrenFromViews == null) {
|
||||
mDetachAllChildrenFromViews = mOperationsQueue.enqueueDetachAllChildrenFromViews();
|
||||
}
|
||||
|
||||
if (oldNativeChildren.length != 0) {
|
||||
mViewsToDetachAllChildrenFrom.add(node);
|
||||
}
|
||||
|
||||
int tag = node.getReactTag();
|
||||
int numViewsToAdd = newNativeChildren.length;
|
||||
final int[] viewsToAdd;
|
||||
if (numViewsToAdd == 0) {
|
||||
viewsToAdd = EMPTY_INT_ARRAY;
|
||||
} else {
|
||||
viewsToAdd = new int[numViewsToAdd];
|
||||
int i = 0;
|
||||
for (FlatShadowNode child : newNativeChildren) {
|
||||
if (child.getNativeParentTag() == tag) {
|
||||
viewsToAdd[i] = -child.getReactTag();
|
||||
} else {
|
||||
viewsToAdd[i] = child.getReactTag();
|
||||
}
|
||||
// all views we add are first start detached
|
||||
child.setNativeParentTag(-1);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate an array of views to detach.
|
||||
// These views still have their native parent set as opposed to being reset to -1
|
||||
for (FlatShadowNode child : oldNativeChildren) {
|
||||
if (child.getNativeParentTag() == tag) {
|
||||
// View is attached to old parent and needs to be removed.
|
||||
mViewsToDetach.add(child);
|
||||
child.setNativeParentTag(-1);
|
||||
}
|
||||
}
|
||||
|
||||
final int[] viewsToDetach = collectViewTags(mViewsToDetach);
|
||||
mViewsToDetach.clear();
|
||||
|
||||
// restore correct parent tag
|
||||
for (FlatShadowNode child : newNativeChildren) {
|
||||
child.setNativeParentTag(tag);
|
||||
}
|
||||
|
||||
mOperationsQueue.enqueueUpdateViewGroup(tag, viewsToAdd, viewsToDetach);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively walks node tree from a given node and collects draw commands, listeners, node
|
||||
* regions and native children. Calls collect state on the node, then processNodeAndCollectState
|
||||
* for the recursion.
|
||||
*/
|
||||
private boolean collectStateRecursively(
|
||||
FlatShadowNode node,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom,
|
||||
boolean isAndroidView,
|
||||
boolean needsCustomLayoutForChildren) {
|
||||
if (node.hasNewLayout()) {
|
||||
node.markLayoutSeen();
|
||||
}
|
||||
|
||||
float roundedLeft = roundToPixel(left);
|
||||
float roundedTop = roundToPixel(top);
|
||||
float roundedRight = roundToPixel(right);
|
||||
float roundedBottom = roundToPixel(bottom);
|
||||
|
||||
// notify JS about layout event if requested
|
||||
if (node.shouldNotifyOnLayout()) {
|
||||
OnLayoutEvent layoutEvent = node.obtainLayoutEvent(
|
||||
Math.round(node.getLayoutX()),
|
||||
Math.round(node.getLayoutY()),
|
||||
(int) (roundedRight - roundedLeft),
|
||||
(int) (roundedBottom - roundedTop));
|
||||
if (layoutEvent != null) {
|
||||
mOnLayoutEvents.add(layoutEvent);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.clipToBounds()) {
|
||||
clipLeft = Math.max(left, clipLeft);
|
||||
clipTop = Math.max(top, clipTop);
|
||||
clipRight = Math.min(right, clipRight);
|
||||
clipBottom = Math.min(bottom, clipBottom);
|
||||
}
|
||||
|
||||
node.collectState(
|
||||
this,
|
||||
roundedLeft,
|
||||
roundedTop,
|
||||
roundedRight,
|
||||
roundedBottom,
|
||||
roundToPixel(clipLeft),
|
||||
roundToPixel(clipTop),
|
||||
roundToPixel(clipRight),
|
||||
clipBottom);
|
||||
|
||||
boolean updated = false;
|
||||
for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) {
|
||||
ReactShadowNode child = node.getChildAt(i);
|
||||
if (child.isVirtual()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
updated |= processNodeAndCollectState(
|
||||
(FlatShadowNode) child,
|
||||
left,
|
||||
top,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom,
|
||||
isAndroidView,
|
||||
needsCustomLayoutForChildren);
|
||||
}
|
||||
|
||||
node.resetUpdated();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects state and enqueues View boundary updates for a given node tree. Returns true if
|
||||
* this node or any of its descendants that mount to View generated any updates.
|
||||
*/
|
||||
private boolean processNodeAndCollectState(
|
||||
FlatShadowNode node,
|
||||
float parentLeft,
|
||||
float parentTop,
|
||||
float parentClipLeft,
|
||||
float parentClipTop,
|
||||
float parentClipRight,
|
||||
float parentClipBottom,
|
||||
boolean parentIsAndroidView,
|
||||
boolean needsCustomLayout) {
|
||||
float width = node.getLayoutWidth();
|
||||
float height = node.getLayoutHeight();
|
||||
|
||||
float left = parentLeft + node.getLayoutX();
|
||||
float top = parentTop + node.getLayoutY();
|
||||
float right = left + width;
|
||||
float bottom = top + height;
|
||||
|
||||
boolean mountsToView = node.mountsToView();
|
||||
|
||||
final boolean updated;
|
||||
|
||||
if (!parentIsAndroidView) {
|
||||
addNodeRegion(node, left, top, right, bottom, !mountsToView);
|
||||
}
|
||||
|
||||
if (mountsToView) {
|
||||
ensureBackingViewIsCreated(node);
|
||||
|
||||
addNativeChild(node);
|
||||
updated = collectStateForMountableNode(
|
||||
node,
|
||||
0, // left - left
|
||||
0, // top - top
|
||||
right - left,
|
||||
bottom - top,
|
||||
parentClipLeft - left,
|
||||
parentClipTop - top,
|
||||
parentClipRight - left,
|
||||
parentClipBottom - top);
|
||||
|
||||
if (!parentIsAndroidView) {
|
||||
mDrawCommands.add(node.collectDrawView(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
parentClipLeft,
|
||||
parentClipTop,
|
||||
parentClipRight,
|
||||
parentClipBottom));
|
||||
}
|
||||
|
||||
if (!needsCustomLayout) {
|
||||
updateViewBounds(node, left, top, right, bottom);
|
||||
}
|
||||
} else {
|
||||
updated = collectStateRecursively(
|
||||
node,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
parentClipLeft,
|
||||
parentClipTop,
|
||||
parentClipRight,
|
||||
parentClipBottom,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private void updateViewPadding(AndroidView androidView, int reactTag) {
|
||||
if (androidView.isPaddingChanged()) {
|
||||
mOperationsQueue.enqueueSetPadding(
|
||||
reactTag,
|
||||
Math.round(androidView.getPadding(Spacing.LEFT)),
|
||||
Math.round(androidView.getPadding(Spacing.TOP)),
|
||||
Math.round(androidView.getPadding(Spacing.RIGHT)),
|
||||
Math.round(androidView.getPadding(Spacing.BOTTOM)));
|
||||
androidView.resetPaddingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] collectViewTags(ArrayList<FlatShadowNode> views) {
|
||||
int numViews = views.size();
|
||||
if (numViews == 0) {
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
int[] viewTags = new int[numViews];
|
||||
for (int i = 0; i < numViews; ++i) {
|
||||
viewTags[i] = views.get(i).getReactTag();
|
||||
}
|
||||
|
||||
return viewTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is what Math.round() does, except it returns float.
|
||||
*/
|
||||
private static float roundToPixel(float pos) {
|
||||
return (float) Math.floor(pos + 0.5f);
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
|
||||
/* package */ final class TextNodeRegion extends NodeRegion {
|
||||
private @Nullable Layout mLayout;
|
||||
|
||||
/* package */ TextNodeRegion(
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
int tag,
|
||||
boolean isVirtual,
|
||||
@Nullable Layout layout) {
|
||||
super(left, top, right, bottom, tag, isVirtual);
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
public void setLayout(Layout layout) {
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
/* package */ @Nullable Layout getLayout() {
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
/* package */ int getReactTag(float touchX, float touchY) {
|
||||
if (mLayout != null) {
|
||||
CharSequence text = mLayout.getText();
|
||||
if (text instanceof Spanned) {
|
||||
int y = Math.round(touchY - getTop());
|
||||
if (y >= mLayout.getLineTop(0) && y < mLayout.getLineBottom(mLayout.getLineCount() - 1)) {
|
||||
float x = Math.round(touchX - getLeft());
|
||||
int line = mLayout.getLineForVertical(y);
|
||||
|
||||
if (mLayout.getLineLeft(line) <= x && x <= mLayout.getLineRight(line)) {
|
||||
int off = mLayout.getOffsetForHorizontal(line, x);
|
||||
|
||||
Spanned spanned = (Spanned) text;
|
||||
RCTRawText[] link = spanned.getSpans(off, off, RCTRawText.class);
|
||||
|
||||
if (link.length != 0) {
|
||||
return link[0].getReactTag();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.getReactTag(touchX, touchY);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean matchesTag(int tag) {
|
||||
if (super.matchesTag(tag)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mLayout != null) {
|
||||
CharSequence text = mLayout.getText();
|
||||
if (text instanceof Spanned) {
|
||||
Spanned spannedText = (Spanned) text;
|
||||
RCTRawText[] spans = spannedText.getSpans(0, text.length(), RCTRawText.class);
|
||||
for (RCTRawText span : spans) {
|
||||
if (span.getReactTag() == tag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* TypefaceCache provides methods to resolve typeface from font family, or existing typeface
|
||||
* with a different style.
|
||||
*/
|
||||
/* package */ final class TypefaceCache {
|
||||
|
||||
private static final int MAX_STYLES = 4; // NORMAL = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3
|
||||
|
||||
private static final HashMap<String, Typeface[]> FONTFAMILY_CACHE = new HashMap<>();
|
||||
private static final HashMap<Typeface, Typeface[]> TYPEFACE_CACHE = new HashMap<>();
|
||||
|
||||
private static final String[] EXTENSIONS = {
|
||||
"",
|
||||
"_bold",
|
||||
"_italic",
|
||||
"_bold_italic"};
|
||||
private static final String[] FILE_EXTENSIONS = {".ttf", ".otf"};
|
||||
private static final String FONTS_ASSET_PATH = "fonts/";
|
||||
|
||||
@Nullable private static AssetManager sAssetManager = null;
|
||||
|
||||
public static void setAssetManager(AssetManager assetManager) {
|
||||
sAssetManager = assetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Typeface for a given a FontFamily and style.
|
||||
*/
|
||||
public static Typeface getTypeface(String fontFamily, int style) {
|
||||
Typeface[] cache = FONTFAMILY_CACHE.get(fontFamily);
|
||||
if (cache == null) {
|
||||
// cache is empty, create one.
|
||||
cache = new Typeface[MAX_STYLES];
|
||||
FONTFAMILY_CACHE.put(fontFamily, cache);
|
||||
} else if (cache[style] != null) {
|
||||
// return cached value.
|
||||
return cache[style];
|
||||
}
|
||||
|
||||
Typeface typeface = createTypeface(fontFamily, style);
|
||||
cache[style] = typeface;
|
||||
TYPEFACE_CACHE.put(typeface, cache);
|
||||
return typeface;
|
||||
}
|
||||
|
||||
private static Typeface createTypeface(String fontFamilyName, int style) {
|
||||
String extension = EXTENSIONS[style];
|
||||
StringBuilder fileNameBuffer = new StringBuilder(32)
|
||||
.append(FONTS_ASSET_PATH)
|
||||
.append(fontFamilyName)
|
||||
.append(extension);
|
||||
int length = fileNameBuffer.length();
|
||||
for (String fileExtension : FILE_EXTENSIONS) {
|
||||
String fileName = fileNameBuffer.append(fileExtension).toString();
|
||||
try {
|
||||
return Typeface.createFromAsset(sAssetManager, fileName);
|
||||
} catch (RuntimeException e) {
|
||||
// unfortunately Typeface.createFromAsset throws an exception instead of returning null
|
||||
// if the typeface doesn't exist
|
||||
fileNameBuffer.setLength(length);
|
||||
}
|
||||
}
|
||||
return Assertions.assumeNotNull(Typeface.create(fontFamilyName, style));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a derivative of a given Typeface with a different style.
|
||||
*/
|
||||
public static Typeface getTypeface(Typeface typeface, int style) {
|
||||
if (typeface == null) {
|
||||
return Typeface.defaultFromStyle(style);
|
||||
}
|
||||
|
||||
Typeface[] cache = TYPEFACE_CACHE.get(typeface);
|
||||
if (cache == null) {
|
||||
// This should not happen because all Typefaces are coming from TypefaceCache,
|
||||
// and thus should be registered in TYPEFACE_CACHE.
|
||||
// If we get here, it's a bug and one of the 2 scenarios happened:
|
||||
// a) TypefaceCache created a Typeface and didn't put it into TYPEFACE_CACHE.
|
||||
// b) someone else created a Typeface bypassing TypefaceCache so it's not registered here.
|
||||
//
|
||||
// If it's not registered, we can just register it manually for consistency, and so that
|
||||
// next time someone requests a un unknown Typeface, it's already cached and we don't create
|
||||
// extra copies.
|
||||
cache = new Typeface[MAX_STYLES];
|
||||
cache[typeface.getStyle()] = typeface;
|
||||
} else if (cache[style] != null) {
|
||||
// return cached value.
|
||||
return cache[style];
|
||||
}
|
||||
|
||||
typeface = Typeface.create(typeface, style);
|
||||
cache[style] = typeface;
|
||||
TYPEFACE_CACHE.put(typeface, cache);
|
||||
return typeface;
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
/**
|
||||
* {@link DrawCommandManager} with vertical clipping (The view scrolls up and down).
|
||||
*/
|
||||
/* package */ final class VerticalDrawCommandManager extends ClippingDrawCommandManager {
|
||||
|
||||
/* package */ VerticalDrawCommandManager(
|
||||
FlatViewGroup flatViewGroup,
|
||||
DrawCommand[] drawCommands) {
|
||||
super(flatViewGroup, drawCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
int commandStartIndex() {
|
||||
int start = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.top);
|
||||
// We don't care whether we matched or not, but positive indices are helpful. The binary search
|
||||
// returns ~index in the case that it isn't a match, so reverse that here.
|
||||
return start < 0 ? ~start : start;
|
||||
}
|
||||
|
||||
@Override
|
||||
int commandStopIndex(int start) {
|
||||
int stop = Arrays.binarySearch(
|
||||
mCommandMinTop,
|
||||
start,
|
||||
mCommandMinTop.length,
|
||||
mClippingRect.bottom);
|
||||
// We don't care whether we matched or not, but positive indices are helpful. The binary search
|
||||
// returns ~index in the case that it isn't a match, so reverse that here.
|
||||
return stop < 0 ? ~stop : stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
int regionStopIndex(float touchX, float touchY) {
|
||||
int stop = Arrays.binarySearch(mRegionMinTop, touchY + 0.0001f);
|
||||
// We don't care whether we matched or not, but positive indices are helpful. The binary search
|
||||
// returns ~index in the case that it isn't a match, so reverse that here.
|
||||
return stop < 0 ? ~stop : stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean regionAboveTouch(int index, float touchX, float touchY) {
|
||||
return mRegionMaxBottom[index] < touchY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the max and min arrays for a given set of node regions.
|
||||
*
|
||||
* This should never be called from the UI thread, as the reason it exists is to do work off the
|
||||
* UI thread.
|
||||
*
|
||||
* @param regions The regions that will eventually be mounted.
|
||||
* @param maxBottom At each index i, the maximum bottom value of all regions at or below i.
|
||||
* @param minTop At each index i, the minimum top value of all regions at or below i.
|
||||
*/
|
||||
public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxBottom, float[] minTop) {
|
||||
float last = 0;
|
||||
for (int i = 0; i < regions.length; i++) {
|
||||
last = Math.max(last, regions[i].getTouchableBottom());
|
||||
maxBottom[i] = last;
|
||||
}
|
||||
for (int i = regions.length - 1; i >= 0; i--) {
|
||||
last = Math.min(last, regions[i].getTouchableTop());
|
||||
minTop[i] = last;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the max and min arrays for a given set of draw commands. Also populates a mapping of
|
||||
* react tags to their index position in the command array.
|
||||
*
|
||||
* This should never be called from the UI thread, as the reason it exists is to do work off the
|
||||
* UI thread.
|
||||
*
|
||||
* @param commands The draw commands that will eventually be mounted.
|
||||
* @param maxBottom At each index i, the maximum bottom value of all draw commands at or below i.
|
||||
* @param minTop At each index i, the minimum top value of all draw commands at or below i.
|
||||
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
|
||||
*/
|
||||
public static void fillMaxMinArrays(
|
||||
DrawCommand[] commands,
|
||||
float[] maxBottom,
|
||||
float[] minTop,
|
||||
SparseIntArray drawViewIndexMap) {
|
||||
float last = 0;
|
||||
// Loop through the DrawCommands, keeping track of the maximum we've seen if we only iterated
|
||||
// through items up to this position.
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
if (commands[i] instanceof DrawView) {
|
||||
DrawView drawView = (DrawView) commands[i];
|
||||
// These will generally be roughly sorted by id, so try to insert at the end if possible.
|
||||
drawViewIndexMap.append(drawView.reactTag, i);
|
||||
last = Math.max(last, drawView.mLogicalBottom);
|
||||
} else {
|
||||
last = Math.max(last, commands[i].getBottom());
|
||||
}
|
||||
maxBottom[i] = last;
|
||||
}
|
||||
// Intentionally leave last as it was, since it's at the maximum bottom position we've seen so
|
||||
// far, we can use it again.
|
||||
|
||||
// Loop through backwards, keeping track of the minimum we've seen at this position.
|
||||
for (int i = commands.length - 1; i >= 0; i--) {
|
||||
if (commands[i] instanceof DrawView) {
|
||||
last = Math.min(last, ((DrawView) commands[i]).mLogicalTop);
|
||||
} else {
|
||||
last = Math.min(last, commands[i].getTop());
|
||||
}
|
||||
minTop[i] = last;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
public interface ViewResolver {
|
||||
public View getView(int tag);
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
/**
|
||||
* Base class to ViewManagers that don't map to a View.
|
||||
*/
|
||||
abstract class VirtualViewManager<C extends FlatShadowNode> extends ViewManager<View, C> {
|
||||
@Override
|
||||
protected View createViewInstance(ThemedReactContext reactContext) {
|
||||
throw new RuntimeException(getName() + " doesn't map to a View");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExtraData(View root, Object extraData) {
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ rn_android_library(
|
|||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/devsupport:devsupport"),
|
||||
react_native_target("java/com/facebook/react/flat:flat"),
|
||||
react_native_target("java/com/facebook/react/module/model:model"),
|
||||
react_native_target("java/com/facebook/react/modules/accessibilityinfo:accessibilityinfo"),
|
||||
react_native_target("java/com/facebook/react/modules/appstate:appstate"),
|
||||
|
|
|
@ -7,23 +7,11 @@
|
|||
|
||||
package com.facebook.react.shell;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import com.facebook.react.LazyReactPackage;
|
||||
import com.facebook.react.animated.NativeAnimatedModule;
|
||||
import com.facebook.react.bridge.ModuleSpec;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.flat.FlatARTSurfaceViewManager;
|
||||
import com.facebook.react.flat.RCTImageViewManager;
|
||||
import com.facebook.react.flat.RCTModalHostManager;
|
||||
import com.facebook.react.flat.RCTRawTextManager;
|
||||
import com.facebook.react.flat.RCTTextInlineImageManager;
|
||||
import com.facebook.react.flat.RCTTextInputManager;
|
||||
import com.facebook.react.flat.RCTTextManager;
|
||||
import com.facebook.react.flat.RCTViewManager;
|
||||
import com.facebook.react.flat.RCTViewPagerManager;
|
||||
import com.facebook.react.flat.RCTVirtualTextManager;
|
||||
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
||||
import com.facebook.react.modules.accessibilityinfo.AccessibilityInfoModule;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
|
@ -332,33 +320,17 @@ public class MainReactPackage extends LazyReactPackage {
|
|||
viewManagers.add(new ReactWebViewManager());
|
||||
viewManagers.add(new SwipeRefreshLayoutManager());
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(reactContext);
|
||||
boolean useFlatUi = preferences.getBoolean("flat_uiimplementation", false);
|
||||
if (useFlatUi) {
|
||||
// Flat managers
|
||||
viewManagers.add(new FlatARTSurfaceViewManager());
|
||||
viewManagers.add(new RCTTextInlineImageManager());
|
||||
viewManagers.add(new RCTImageViewManager());
|
||||
viewManagers.add(new RCTModalHostManager());
|
||||
viewManagers.add(new RCTRawTextManager());
|
||||
viewManagers.add(new RCTTextInputManager());
|
||||
viewManagers.add(new RCTTextManager());
|
||||
viewManagers.add(new RCTViewManager());
|
||||
viewManagers.add(new RCTViewPagerManager());
|
||||
viewManagers.add(new RCTVirtualTextManager());
|
||||
} else {
|
||||
// Native equivalents
|
||||
viewManagers.add(new ARTSurfaceViewManager());
|
||||
viewManagers.add(new FrescoBasedReactTextInlineImageViewManager());
|
||||
viewManagers.add(new ReactImageManager());
|
||||
viewManagers.add(new ReactModalHostManager());
|
||||
viewManagers.add(new ReactRawTextManager());
|
||||
viewManagers.add(new ReactTextInputManager());
|
||||
viewManagers.add(new ReactTextViewManager());
|
||||
viewManagers.add(new ReactViewManager());
|
||||
viewManagers.add(new ReactViewPagerManager());
|
||||
viewManagers.add(new ReactVirtualTextViewManager());
|
||||
}
|
||||
// Native equivalents
|
||||
viewManagers.add(new ARTSurfaceViewManager());
|
||||
viewManagers.add(new FrescoBasedReactTextInlineImageViewManager());
|
||||
viewManagers.add(new ReactImageManager());
|
||||
viewManagers.add(new ReactModalHostManager());
|
||||
viewManagers.add(new ReactRawTextManager());
|
||||
viewManagers.add(new ReactTextInputManager());
|
||||
viewManagers.add(new ReactTextViewManager());
|
||||
viewManagers.add(new ReactViewManager());
|
||||
viewManagers.add(new ReactViewPagerManager());
|
||||
viewManagers.add(new ReactVirtualTextViewManager());
|
||||
|
||||
return viewManagers;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue