Don't clip overflowing Nodes
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:
Родитель
5f162ca119
Коммит
9d67989001
|
@ -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()) {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче