Files
react-native/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java
Denis Koroskin 31d2443dd4 Implement RCTTextInlineImage with Nodes
Summary:
React allows nesting <Image> inside <Text>, in which case it turns into an RCTTextInlineImage element. RNFeed is using it in a few places and thus we need to support it, too.

This diff implements it with InlineImageSpan (WithPipeline, and WithDrawee separately).

Reviewed By: ahmedre

Differential Revision: D2792569
2016-12-19 13:40:18 -08:00

388 lines
12 KiB
Java

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.CatalystStylesDiffMap;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.image.ReactImageManager;
/**
* FlatUIImplementation builds on top of UIImplementation and allows pre-creating everything
* required for drawing (DrawCommands) and touching (NodeRegions) views in background thread
* for faster drawing and interactions.
*/
public class FlatUIImplementation extends UIImplementation {
/**
* This Comparator allows sorting FlatShadowNode by order in which they should be added.
*/
private static final Comparator<FlatShadowNode> COMPARATOR = new Comparator<FlatShadowNode>() {
public int compare(FlatShadowNode lhs, FlatShadowNode rhs) {
return lhs.getMoveToIndexInParent() - rhs.getMoveToIndexInParent();
}
};
/**
* Temporary storage for elements that need to be moved within a parent.
* Only used inside #manageChildren() and always empty outside of it.
*/
private final ArrayList<FlatShadowNode> mNodesToMove = new ArrayList<>();
private final StateBuilder mStateBuilder;
public static FlatUIImplementation createInstance(
ReactApplicationContext reactContext,
List<ViewManager> viewManagers) {
ReactImageManager reactImageManager = findReactImageManager(viewManagers);
if (reactImageManager != null) {
Object callerContext = reactImageManager.getCallerContext();
if (callerContext != null) {
RCTImageView.setCallerContext(callerContext);
}
}
TypefaceCache.setAssetManager(reactContext.getAssets());
viewManagers = new ArrayList<ViewManager>(viewManagers);
viewManagers.add(new RCTViewManager());
viewManagers.add(new RCTTextManager());
viewManagers.add(new RCTRawTextManager());
viewManagers.add(new RCTVirtualTextManager());
viewManagers.add(new RCTTextInlineImageManager());
viewManagers.add(new RCTImageViewManager());
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers);
FlatNativeViewHierarchyManager nativeViewHierarchyManager = new FlatNativeViewHierarchyManager(
viewManagerRegistry);
FlatUIViewOperationQueue operationsQueue = new FlatUIViewOperationQueue(
reactContext,
nativeViewHierarchyManager);
return new FlatUIImplementation(viewManagerRegistry, operationsQueue);
}
private FlatUIImplementation(
ViewManagerRegistry viewManagers,
FlatUIViewOperationQueue operationsQueue) {
super(viewManagers, operationsQueue);
mStateBuilder = new StateBuilder(operationsQueue);
}
@Override
protected ReactShadowNode createRootShadowNode() {
return new FlatRootShadowNode();
}
@Override
protected ReactShadowNode createShadowNode(String className) {
ReactShadowNode cssNode = super.createShadowNode(className);
if (cssNode instanceof FlatShadowNode) {
return cssNode;
}
ViewManager viewManager = resolveViewManager(className);
return new AndroidView(viewManager);
}
protected void handleCreateView(
ReactShadowNode cssNode,
int rootViewTag,
@Nullable CatalystStylesDiffMap styles) {
FlatShadowNode node = (FlatShadowNode) cssNode;
if (styles != null) {
node.handleUpdateProperties(styles);
}
if (node.mountsToView()) {
int tag = cssNode.getReactTag();
mStateBuilder.ensureBackingViewIsCreated(node, tag, styles);
}
}
@Override
protected void handleUpdateView(
ReactShadowNode cssNode,
String className,
CatalystStylesDiffMap styles) {
FlatShadowNode node = (FlatShadowNode) cssNode;
node.handleUpdateProperties(styles);
if (node.mountsToView()) {
int tag = cssNode.getReactTag();
mStateBuilder.ensureBackingViewIsCreated(node, tag, 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);
}
/**
* 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;
int moveFromIndex;
int moveFromChildIndex;
if (moveFrom == null) {
moveFromIndex = -1;
moveFromChildIndex = -1;
} else {
moveFromIndex = moveFrom.size() - 1;
moveFromChildIndex = moveFrom.getInt(moveFromIndex);
}
int removeFromIndex;
int removeFromChildIndex;
if (removeFrom == null) {
removeFromIndex = -1;
removeFromChildIndex = -1;
} else {
removeFromIndex = removeFrom.size() - 1;
removeFromChildIndex = removeFrom.getInt(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) {
int indexInParent = moveTo.getInt(moveFromIndex);
moveChild(removeChildAt(parentNode, moveFromChildIndex, prevIndex), indexInParent);
prevIndex = moveFromChildIndex;
--moveFromIndex;
moveFromChildIndex = (moveFromIndex == -1) ? -1 : moveFrom.getInt(moveFromIndex);
} else if (removeFromChildIndex > moveFromChildIndex) {
removeChild(removeChildAt(parentNode, removeFromChildIndex, prevIndex));
prevIndex = removeFromChildIndex;
--removeFromIndex;
removeFromChildIndex = (removeFromIndex == -1) ? -1 : removeFrom.getInt(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(FlatShadowNode child) {
if (child.mountsToView()) {
// this will recursively drop all subviews
mStateBuilder.dropView(child);
}
removeShadowNode(child);
}
/**
* Prepares a given element to be moved to a new position.
*/
private void moveChild(FlatShadowNode child, int indexInParent) {
child.setMoveToIndexInParent(indexInParent);
mNodesToMove.add(child);
}
/**
* Adds all children from addChildTags and mNodesToMove, populated by removeChildren.
*/
private void addChildren(
ReactShadowNode parentNode,
@Nullable ReadableArray addChildTags,
@Nullable ReadableArray addAtIndices) {
int prevIndex = -1;
int numNodesToMove = mNodesToMove.size();
int moveToIndex;
int moveToChildIndex;
FlatShadowNode moveToChild;
if (numNodesToMove == 0) {
moveToIndex = Integer.MAX_VALUE;
moveToChild = null;
moveToChildIndex = Integer.MAX_VALUE;
} else {
if (numNodesToMove > 1) {
// mNodesToMove is not sorted, so do it now.
Collections.sort(mNodesToMove, COMPARATOR);
}
moveToIndex = 0;
moveToChild = mNodesToMove.get(0);
moveToChildIndex = moveToChild.getMoveToIndexInParent();
}
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 mNodesToMove 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) {
addChildAt(parentNode, moveToChild, moveToChildIndex, prevIndex);
prevIndex = moveToChildIndex;
++moveToIndex;
if (moveToIndex == numNodesToMove) {
moveToChildIndex = Integer.MAX_VALUE;
} else {
moveToChild = mNodesToMove.get(moveToIndex);
moveToChildIndex = moveToChild.getMoveToIndexInParent();
}
} 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;
}
}
mNodesToMove.clear();
}
/**
* Removes a child from parent, verifying that we are removing in descending order.
*/
private static FlatShadowNode removeChildAt(
ReactShadowNode parentNode,
int index,
int prevIndex) {
if (index >= prevIndex) {
throw new RuntimeException(
"Invariant failure, needs sorting! " + index + " >= " + prevIndex);
}
return (FlatShadowNode) 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 calculateRootLayout(ReactShadowNode cssRoot) {
}
@Override
protected void applyUpdatesRecursive(
ReactShadowNode cssNode,
float absoluteX,
float absoluteY,
EventDispatcher eventDispatcher) {
FlatRootShadowNode rootNode = (FlatRootShadowNode) cssNode;
if (!rootNode.needsLayout() && !rootNode.isUpdated()) {
return;
}
super.calculateRootLayout(rootNode);
rootNode.markUpdated(false);
mStateBuilder.applyUpdates(eventDispatcher, rootNode);
}
@Override
public void setJSResponder(int possiblyVirtualReactTag, boolean blockNativeResponder) {
ReactShadowNode node = resolveShadowNode(possiblyVirtualReactTag);
while (node.isVirtual()) {
node = node.getParent();
}
FlatShadowNode nonVirtualNode = (FlatShadowNode) node;
int nonVirtualTag = nonVirtualNode.getReactTag();
nonVirtualNode.forceMountToView();
mStateBuilder.ensureBackingViewIsCreated(nonVirtualNode, nonVirtualTag, null);
FlatUIViewOperationQueue operationsQueue = mStateBuilder.getOperationsQueue();
operationsQueue.enqueueSetJSResponder(
nonVirtualTag,
possiblyVirtualReactTag,
blockNativeResponder);
}
private static @Nullable ReactImageManager findReactImageManager(List<ViewManager> viewManagers) {
for (int i = 0, size = viewManagers.size(); i != size; ++i) {
if (viewManagers.get(i) instanceof ReactImageManager) {
return (ReactImageManager) viewManagers.get(i);
}
}
return null;
}
}