mirror of
https://github.com/status-im/react-native.git
synced 2025-01-29 18:54:58 +00:00
Implement RemoveClippedSubviews for Nodes
Summary: RN has an optimization in which a ScrollView (or similar ViewGroups) can ask to remove clipped subviews from the View hierarchy. This patch implements this optimization for Nodes, but instead of adding and removing the Views, it attaches and detaches Views instead. Note that this patch does not handle overflow: visible. This is addressed in a stacked patch on top of this patch (to simplify the review process). Reviewed By: astreet Differential Revision: D3235050
This commit is contained in:
parent
96cb8165c8
commit
5f162ca119
@ -13,9 +13,11 @@ import android.graphics.Canvas;
|
||||
|
||||
/* package */ final class DrawView extends AbstractClippingDrawCommand {
|
||||
|
||||
/* package */ static final DrawView INSTANCE = new DrawView(0, 0, 0, 0);
|
||||
/* package */ final int reactTag;
|
||||
/* package */ boolean isViewGroupClipped;
|
||||
|
||||
public DrawView(float clipLeft, float clipTop, float clipRight, float clipBottom) {
|
||||
public DrawView(int reactTag, float clipLeft, float clipTop, float clipRight, float clipBottom) {
|
||||
this.reactTag = reactTag;
|
||||
setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactStylesDiffMap;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
|
||||
/**
|
||||
* FlatShadowNode is a base class for all shadow node used in FlatUIImplementation. It extends
|
||||
@ -36,6 +37,8 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";
|
||||
private static final String PROP_TEST_ID = "testID";
|
||||
private static final String PROP_TRANSFORM = "transform";
|
||||
private static final String PROP_REMOVE_CLIPPED_SUBVIEWS =
|
||||
ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS;
|
||||
|
||||
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
|
||||
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
|
||||
@ -74,7 +77,8 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
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_IMPORTANT_FOR_ACCESSIBILITY) ||
|
||||
styles.hasKey(PROP_REMOVE_CLIPPED_SUBVIEWS)) {
|
||||
forceMountToView();
|
||||
}
|
||||
}
|
||||
@ -317,7 +321,7 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
}
|
||||
|
||||
if (mDrawView == null) {
|
||||
mDrawView = DrawView.INSTANCE;
|
||||
mDrawView = new DrawView(getReactTag(), 0, 0, 0, 0);
|
||||
invalidate();
|
||||
|
||||
// reset NodeRegion to allow it getting garbage-collected
|
||||
@ -327,7 +331,7 @@ import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
/* package */ final DrawView collectDrawView(float left, float top, float right, float bottom) {
|
||||
if (!Assertions.assumeNotNull(mDrawView).clipBoundsMatch(left, top, right, bottom)) {
|
||||
mDrawView = new DrawView(left, top, right, bottom);
|
||||
mDrawView = new DrawView(getReactTag(), left, top, right, bottom);
|
||||
}
|
||||
|
||||
return mDrawView;
|
||||
|
@ -13,6 +13,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.NoSuchNativeViewException;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
|
||||
@ -180,8 +181,15 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
// Measure native View
|
||||
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
|
||||
try {
|
||||
// Measure native 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];
|
||||
|
@ -13,6 +13,8 @@ import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@ -22,7 +24,9 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.common.SystemClock;
|
||||
@ -33,13 +37,16 @@ import com.facebook.react.uimanager.ReactCompoundViewGroup;
|
||||
import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.views.image.ImageLoadEvent;
|
||||
import com.facebook.react.views.view.ReactClippingViewGroup;
|
||||
import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
|
||||
/**
|
||||
* A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over
|
||||
* array of DrawCommands, executing them one by one.
|
||||
*/
|
||||
/* package */ final class FlatViewGroup extends ViewGroup
|
||||
implements ReactInterceptingViewGroup, ReactCompoundViewGroup, ReactPointerEventsView {
|
||||
implements ReactInterceptingViewGroup, ReactClippingViewGroup,
|
||||
ReactCompoundViewGroup, ReactPointerEventsView {
|
||||
/**
|
||||
* Helper class that allows AttachDetachListener to invalidate the hosting View.
|
||||
*/
|
||||
@ -88,6 +95,12 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
private long mLastTouchDownTime;
|
||||
private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
|
||||
|
||||
private boolean mRemoveClippedSubviews;
|
||||
private @Nullable Rect mClippingRect;
|
||||
// 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, FlatViewGroup> mClippedSubviews = new HashMap<>();
|
||||
|
||||
/* package */ FlatViewGroup(Context context) {
|
||||
super(context);
|
||||
setClipChildren(false);
|
||||
@ -144,8 +157,21 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
for (DrawCommand drawCommand : mDrawCommands) {
|
||||
drawCommand.draw(this, canvas);
|
||||
if (mRemoveClippedSubviews) {
|
||||
for (DrawCommand drawCommand : mDrawCommands) {
|
||||
if (drawCommand instanceof DrawView) {
|
||||
if (!((DrawView) drawCommand).isViewGroupClipped) {
|
||||
drawCommand.draw(this, canvas);
|
||||
}
|
||||
// else, don't draw, and don't increment index
|
||||
} else {
|
||||
drawCommand.draw(this, canvas);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (DrawCommand drawCommand : mDrawCommands) {
|
||||
drawCommand.draw(this, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
if (mDrawChildIndex != getChildCount()) {
|
||||
@ -187,6 +213,10 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
|
||||
super.onAttachedToWindow();
|
||||
dispatchOnAttached(mAttachDetachListeners);
|
||||
|
||||
if (mRemoveClippedSubviews) {
|
||||
updateClippingRect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -207,6 +237,10 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
mHotspot.setBounds(0, 0, w, h);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
if (mRemoveClippedSubviews) {
|
||||
updateClippingRect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -358,6 +392,15 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
|
||||
/* package */ void mountDrawCommands(DrawCommand[] drawCommands) {
|
||||
mDrawCommands = drawCommands;
|
||||
if (mRemoveClippedSubviews) {
|
||||
mDrawViewMap.clear();
|
||||
for (DrawCommand drawCommand : mDrawCommands) {
|
||||
if (drawCommand instanceof DrawView) {
|
||||
DrawView drawView = (DrawView) drawCommand;
|
||||
mDrawViewMap.put(drawView.reactTag, drawView);
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@ -393,11 +436,27 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
} else {
|
||||
View view = ensureViewHasNoParent(viewResolver.getView(-viewToAdd));
|
||||
attachViewToParent(view, -1, ensureLayoutParams(view.getLayoutParams()));
|
||||
if (mRemoveClippedSubviews) {
|
||||
mClippedSubviews.remove(-viewToAdd);
|
||||
DrawView drawView = mDrawViewMap.get(-viewToAdd);
|
||||
if (drawView != null) {
|
||||
drawView.isViewGroupClipped = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int viewToDetach : viewsToDetach) {
|
||||
removeDetachedView(viewResolver.getView(viewToDetach), false);
|
||||
View view = viewResolver.getView(viewToDetach);
|
||||
if (view.getParent() != null) {
|
||||
removeViewInLayout(view);
|
||||
} else {
|
||||
removeDetachedView(view, false);
|
||||
}
|
||||
|
||||
if (mRemoveClippedSubviews) {
|
||||
mClippedSubviews.remove(viewToDetach);
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
@ -493,4 +552,98 @@ import com.facebook.react.views.image.ImageLoadEvent;
|
||||
}
|
||||
return generateDefaultLayoutParams();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateClippingRect() {
|
||||
if (!mRemoveClippedSubviews) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assertions.assertNotNull(mClippingRect);
|
||||
ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
|
||||
if (getParent() != null && mClippingRect.top != mClippingRect.bottom) {
|
||||
updateClippingToRect(mClippingRect);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateClippingToRect(Rect clippingRect) {
|
||||
int index = 0;
|
||||
boolean needsInvalidate = false;
|
||||
for (DrawCommand drawCommand : mDrawCommands) {
|
||||
if (drawCommand instanceof DrawView) {
|
||||
DrawView drawView = (DrawView) drawCommand;
|
||||
FlatViewGroup flatViewGroup = mClippedSubviews.get(drawView.reactTag);
|
||||
if (flatViewGroup != null) {
|
||||
// invisible
|
||||
if (clippingRect.intersects(
|
||||
flatViewGroup.getLeft(),
|
||||
flatViewGroup.getTop(),
|
||||
flatViewGroup.getRight(),
|
||||
flatViewGroup.getBottom())) {
|
||||
// now on the screen
|
||||
attachViewToParent(
|
||||
flatViewGroup,
|
||||
index++,
|
||||
ensureLayoutParams(flatViewGroup.getLayoutParams()));
|
||||
mClippedSubviews.remove(flatViewGroup.getId());
|
||||
drawView.isViewGroupClipped = false;
|
||||
needsInvalidate = true;
|
||||
}
|
||||
} else {
|
||||
// visible
|
||||
View view = getChildAt(index++);
|
||||
if (view instanceof FlatViewGroup) {
|
||||
FlatViewGroup flatChildView = (FlatViewGroup) view;
|
||||
Animation animation = flatChildView.getAnimation();
|
||||
boolean isAnimating = animation != null && !animation.hasEnded();
|
||||
if (!isAnimating &&
|
||||
!clippingRect.intersects(
|
||||
view.getLeft(),
|
||||
view.getTop(),
|
||||
view.getRight(),
|
||||
view.getBottom())) {
|
||||
// now off the screen
|
||||
mClippedSubviews.put(view.getId(), flatChildView);
|
||||
detachViewFromParent(view);
|
||||
drawView.isViewGroupClipped = true;
|
||||
index--;
|
||||
needsInvalidate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsInvalidate) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getClippingRect(Rect outClippingRect) {
|
||||
outClippingRect.set(mClippingRect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
|
||||
if (removeClippedSubviews == mRemoveClippedSubviews) {
|
||||
return;
|
||||
}
|
||||
mRemoveClippedSubviews = removeClippedSubviews;
|
||||
if (removeClippedSubviews) {
|
||||
mClippingRect = new Rect();
|
||||
updateClippingRect();
|
||||
} else {
|
||||
// Add all clipped views back, deallocate additional arrays, remove layoutChangeListener
|
||||
Assertions.assertNotNull(mClippingRect);
|
||||
getDrawingRect(mClippingRect);
|
||||
updateClippingToRect(mClippingRect);
|
||||
mClippingRect = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRemoveClippedSubviews() {
|
||||
return mRemoveClippedSubviews;
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,9 @@ 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.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.views.view.ReactDrawableHelper;
|
||||
|
||||
/**
|
||||
@ -102,6 +103,11 @@ import com.facebook.react.views.view.ReactDrawableHelper;
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user