Summary:
As of D3235050, Nodes supports the optimization of removing clipped
subviews from the hierarchy. However, because Nodes supports overflow:visible,
this could cause issues when DrawCommands overflow the bounds of their parent
container. This patch fixes this by not clipping any overflowing Nodes.

Reviewed By: astreet

Differential Revision: D3235072
This commit is contained in:
Ahmed El-Helw 2016-05-13 17:49:38 -07:00
Родитель 5f162ca119
Коммит 9d67989001
5 изменённых файлов: 83 добавлений и 8 удалений

Просмотреть файл

@ -64,10 +64,11 @@ import com.facebook.react.uimanager.ViewManagerRegistry;
int reactTag, int reactTag,
@Nullable DrawCommand[] drawCommands, @Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners, @Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) { @Nullable NodeRegion[] nodeRegions,
boolean hasOverflowingElements) {
FlatViewGroup view = (FlatViewGroup) resolveView(reactTag); FlatViewGroup view = (FlatViewGroup) resolveView(reactTag);
if (drawCommands != null) { if (drawCommands != null) {
view.mountDrawCommands(drawCommands); view.mountDrawCommands(drawCommands, hasOverflowingElements);
} }
if (listeners != null) { if (listeners != null) {
view.mountAttachDetachListeners(listeners); view.mountAttachDetachListeners(listeners);

Просмотреть файл

@ -60,6 +60,11 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
private float mClipRight; private float mClipRight;
private float mClipBottom; 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;
// last OnLayoutEvent info, only used when shouldNotifyOnLayout() is true. // last OnLayoutEvent info, only used when shouldNotifyOnLayout() is true.
private int mLayoutX; private int mLayoutX;
private int mLayoutY; private int mLayoutY;
@ -120,6 +125,11 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
@ReactProp(name = "overflow") @ReactProp(name = "overflow")
public final void setOverflow(String overflow) { public final void setOverflow(String overflow) {
mClipToBounds = "hidden".equals(overflow); mClipToBounds = "hidden".equals(overflow);
if (mClipToBounds) {
mOverflowsContainer = false;
} else {
updateOverflowsContainer();
}
invalidate(); invalidate();
} }
@ -255,6 +265,51 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
/* package */ final void setNodeRegions(NodeRegion[] nodeRegion) { /* package */ final void setNodeRegions(NodeRegion[] nodeRegion) {
mNodeRegions = nodeRegion; mNodeRegions = nodeRegion;
updateOverflowsContainer();
}
/* package */ final void updateOverflowsContainer() {
boolean overflowsContainer = false;
int width = (int) (mNodeRegion.mRight - mNodeRegion.mLeft);
int height = (int) (mNodeRegion.mBottom - mNodeRegion.mTop);
if (!mClipToBounds && height > 0 && width > 0) {
for (NodeRegion region : mNodeRegions) {
if (region.mBottom - region.mTop > height || region.mRight - region.mLeft > width) {
overflowsContainer = true;
break;
}
}
}
// 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) {
int children = getChildCount();
for (int i = 0; i < children; i++) {
ReactShadowNode node = getChildAt(i);
if (node instanceof FlatShadowNode && ((FlatShadowNode) node).mOverflowsContainer) {
overflowsContainer = true;
break;
}
}
}
// 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;
}
}
/* package */ final boolean getOverflowsContainer() {
return mOverflowsContainer;
} }
/* package */ void updateNodeRegion( /* package */ void updateNodeRegion(

Просмотреть файл

@ -44,16 +44,19 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
private final @Nullable DrawCommand[] mDrawCommands; private final @Nullable DrawCommand[] mDrawCommands;
private final @Nullable AttachDetachListener[] mAttachDetachListeners; private final @Nullable AttachDetachListener[] mAttachDetachListeners;
private final @Nullable NodeRegion[] mNodeRegions; private final @Nullable NodeRegion[] mNodeRegions;
private final boolean mHasOverflowingElements;
private UpdateMountState( private UpdateMountState(
int reactTag, int reactTag,
@Nullable DrawCommand[] drawCommands, @Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners, @Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) { @Nullable NodeRegion[] nodeRegions,
boolean hasOverflowingElements) {
mReactTag = reactTag; mReactTag = reactTag;
mDrawCommands = drawCommands; mDrawCommands = drawCommands;
mAttachDetachListeners = listeners; mAttachDetachListeners = listeners;
mNodeRegions = nodeRegions; mNodeRegions = nodeRegions;
mHasOverflowingElements = hasOverflowingElements;
} }
@Override @Override
@ -62,7 +65,8 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
mReactTag, mReactTag,
mDrawCommands, mDrawCommands,
mAttachDetachListeners, mAttachDetachListeners,
mNodeRegions); mNodeRegions,
mHasOverflowingElements);
} }
} }
@ -234,8 +238,14 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
int reactTag, int reactTag,
@Nullable DrawCommand[] drawCommands, @Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners, @Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) { @Nullable NodeRegion[] nodeRegions,
enqueueUIOperation(new UpdateMountState(reactTag, drawCommands, listeners, nodeRegions)); boolean hasOverflowingElements) {
enqueueUIOperation(new UpdateMountState(
reactTag,
drawCommands,
listeners,
nodeRegions,
hasOverflowingElements));
} }
public void enqueueUpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) { public void enqueueUpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {

Просмотреть файл

@ -100,6 +100,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
// lookups in o(1) instead of o(log n) - trade space for time // lookups in o(1) instead of o(log n) - trade space for time
private final Map<Integer, DrawView> mDrawViewMap = new HashMap<>(); private final Map<Integer, DrawView> mDrawViewMap = new HashMap<>();
private final Map<Integer, FlatViewGroup> mClippedSubviews = new HashMap<>(); private final Map<Integer, FlatViewGroup> mClippedSubviews = new HashMap<>();
// whether or not this FlatViewGroup has elements that overflow its bounds
private boolean mHasOverflowingElements;
/* package */ FlatViewGroup(Context context) { /* package */ FlatViewGroup(Context context) {
super(context); super(context);
@ -390,8 +392,9 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
++mDrawChildIndex; ++mDrawChildIndex;
} }
/* package */ void mountDrawCommands(DrawCommand[] drawCommands) { /* package */ void mountDrawCommands(DrawCommand[] drawCommands, boolean hasOverflowingElements) {
mDrawCommands = drawCommands; mDrawCommands = drawCommands;
mHasOverflowingElements = hasOverflowingElements;
if (mRemoveClippedSubviews) { if (mRemoveClippedSubviews) {
mDrawViewMap.clear(); mDrawViewMap.clear();
for (DrawCommand drawCommand : mDrawCommands) { for (DrawCommand drawCommand : mDrawCommands) {
@ -597,6 +600,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
Animation animation = flatChildView.getAnimation(); Animation animation = flatChildView.getAnimation();
boolean isAnimating = animation != null && !animation.hasEnded(); boolean isAnimating = animation != null && !animation.hasEnded();
if (!isAnimating && if (!isAnimating &&
!flatChildView.mHasOverflowingElements &&
!clippingRect.intersects( !clippingRect.intersects(
view.getLeft(), view.getLeft(),
view.getTop(), view.getTop(),

Просмотреть файл

@ -325,6 +325,10 @@ import com.facebook.react.uimanager.events.EventDispatcher;
if (nodeRegions != null) { if (nodeRegions != null) {
shouldUpdateMountState = true; shouldUpdateMountState = true;
node.setNodeRegions(nodeRegions); 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();
} }
if (shouldUpdateMountState) { if (shouldUpdateMountState) {
@ -332,7 +336,8 @@ import com.facebook.react.uimanager.events.EventDispatcher;
node.getReactTag(), node.getReactTag(),
drawCommands, drawCommands,
listeners, listeners,
nodeRegions); nodeRegions,
node.getOverflowsContainer());
} }
if (node.hasUnseenUpdates()) { if (node.hasUnseenUpdates()) {