Support vertical and horizontal clipping with draw command managers.

Summary: Adds support for horizontal clipping, though the FlatViewGroup needs to be made aware still of which it is.

Reviewed By: ahmedre

Differential Revision: D3673501
This commit is contained in:
Seth Kirby 2016-08-08 17:53:57 -07:00 committed by Ahmed El-Helw
parent 192c99a4f6
commit a4c4a88e27
11 changed files with 309 additions and 418 deletions

View File

@ -12,7 +12,6 @@ package com.facebook.react.flat;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -31,15 +30,15 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
* Implementation of a {@link DrawCommandManager} with clipping. Performs drawing by iterating * Implementation of a {@link DrawCommandManager} with clipping. Performs drawing by iterating
* over an array of DrawCommands, executing them one by one except when the commands are clipped. * over an array of DrawCommands, executing them one by one except when the commands are clipped.
*/ */
/* package */ final class ClippingDrawCommandManager extends DrawCommandManager { /* package */ abstract class ClippingDrawCommandManager extends DrawCommandManager {
private final FlatViewGroup mFlatViewGroup; private final FlatViewGroup mFlatViewGroup;
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY; private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private float[] mCommandMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY; protected float[] mCommandMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
private float[] mCommandMinTop = StateBuilder.EMPTY_FLOAT_ARRAY; protected float[] mCommandMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY; private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
private float[] mRegionMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY; protected float[] mRegionMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
private float[] mRegionMinTop = StateBuilder.EMPTY_FLOAT_ARRAY; protected float[] mRegionMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
// Onscreen bounds of draw command array. // Onscreen bounds of draw command array.
private int mStart; private int mStart;
@ -51,7 +50,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
// Map of views that are currently clipped. // Map of views that are currently clipped.
private final Map<Integer, View> mClippedSubviews = new HashMap<>(); private final Map<Integer, View> mClippedSubviews = new HashMap<>();
private final Rect mClippingRect = new Rect(); protected final Rect mClippingRect = new Rect();
// Used in updating the clipping rect, as sometimes we want to detach all views, which means we // 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 // need to temporarily store the views we are detaching and removing. These are always of size
@ -74,6 +73,33 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
updateClippingRect(); 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 @Override
public void mountDrawCommands( public void mountDrawCommands(
DrawCommand[] drawCommands, DrawCommand[] drawCommands,
@ -86,20 +112,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
mCommandMinTop = minTop; mCommandMinTop = minTop;
mDrawViewIndexMap = drawViewIndexMap; mDrawViewIndexMap = drawViewIndexMap;
if (mClippingRect.bottom != mClippingRect.top) { if (mClippingRect.bottom != mClippingRect.top) {
mStart = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.top); mStart = commandStartIndex();
if (mStart < 0) { mStop = commandStopIndex(mStart);
// We don't care whether we matched or not, but positive indices are helpful.
mStart = ~mStart;
}
mStop = Arrays.binarySearch(
mCommandMinTop,
mStart,
mCommandMinTop.length,
mClippingRect.bottom);
if (mStop < 0) {
// We don't care whether we matched or not, but positive indices are helpful.
mStop = ~mStop;
}
if (!willMountViews) { if (!willMountViews) {
// If we are not mounting views, we still need to update view indices and positions. It is // 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 // possible that a child changed size and we still need new clipping even though we are not
@ -118,18 +132,14 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
@Override @Override
public @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY) { public @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY) {
int i = Arrays.binarySearch(mRegionMinTop, touchY + 0.0001f); int i = regionStopIndex(touchX, touchY);
if (i < 0) {
// We don't care whether we matched or not, but positive indices are helpful.
i = ~i;
}
while (i-- > 0) { while (i-- > 0) {
NodeRegion nodeRegion = mNodeRegions[i]; NodeRegion nodeRegion = mNodeRegions[i];
if (!nodeRegion.mIsVirtual) { if (!nodeRegion.mIsVirtual) {
// only interested in virtual nodes // only interested in virtual nodes
continue; continue;
} }
if (mRegionMaxBottom[i] < touchY) { if (regionAboveTouch(i, touchX, touchY)) {
break; break;
} }
if (nodeRegion.withinBounds(touchX, touchY)) { if (nodeRegion.withinBounds(touchX, touchY)) {
@ -142,14 +152,10 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
@Override @Override
public @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY) { public @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY) {
int i = Arrays.binarySearch(mRegionMinTop, touchY + 0.0001f); int i = regionStopIndex(touchX, touchY);
if (i < 0) {
// We don't care whether we matched or not, but positive indices are helpful.
i = ~i;
}
while (i-- > 0) { while (i-- > 0) {
NodeRegion nodeRegion = mNodeRegions[i]; NodeRegion nodeRegion = mNodeRegions[i];
if (mRegionMaxBottom[i] < touchY) { if (regionAboveTouch(i, touchX, touchY)) {
break; break;
} }
if (nodeRegion.withinBounds(touchX, touchY)) { if (nodeRegion.withinBounds(touchX, touchY)) {
@ -250,7 +256,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
} }
// Returns true if a view is currently animating. // Returns true if a view is currently animating.
static boolean animating(View view) { private static boolean animating(View view) {
Animation animation = view.getAnimation(); Animation animation = view.getAnimation();
return animation != null && !animation.hasEnded(); return animation != null && !animation.hasEnded();
} }
@ -269,22 +275,11 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
return false; return false;
} }
int start = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.top); int start = commandStartIndex();
if (start < 0) { int stop = commandStopIndex(start);
// We don't care whether we matched or not, but positive indices are helpful.
start = ~start;
}
int stop = Arrays.binarySearch(
mCommandMinTop,
start,
mCommandMinTop.length,
mClippingRect.bottom);
if (stop < 0) {
// We don't care whether we matched or not, but positive indices are helpful.
stop = ~stop;
}
if (mStart <= start && stop <= mStop) { if (mStart <= start && stop <= mStop) {
// We would only be removing children, don't invalidate and don't bother changing the
// attached children.
return false; return false;
} }
@ -382,21 +377,24 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
int commandIndex = mStart; int commandIndex = mStart;
int size = mFlatViewGroup.getChildCount(); 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++) { 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()); int viewIndex = mDrawViewIndexMap.get(mFlatViewGroup.getChildAt(i).getId());
if (mStop < viewIndex) { 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) { while (commandIndex < mStop) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas); mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
} }
// We are now out of commands to draw, so we can just draw the remaining attached children. // We are now out of commands to draw, so we could just draw the remaining attached
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas); // children, but the for loop logic will draw the rest anyway.
while (++i != size) {
viewIndex = mDrawViewIndexMap.get(mFlatViewGroup.getChildAt(i).getId());
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas);
}
// Everything is drawn, lets get out of here.
return;
} else if (commandIndex <= viewIndex) { } 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) { while (commandIndex < viewIndex) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas); mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
} }
@ -406,7 +404,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas); mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas);
} }
// We have drawn all the views, now just draw the remaining draw commands. // If we get here, it means we have drawn all the views, now just draw the remaining draw
// commands.
while (commandIndex < mStop) { while (commandIndex < mStop) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas); mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
} }

View File

@ -1,235 +0,0 @@
/**
* 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 java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.Animation;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.views.view.ReactClippingViewGroupHelper;
/**
* Abstract {@link DrawCommandManager} with directional clipping.
*/
/* package */ abstract class DirectionalClippingDrawCommandManager {
// This will be fixed in the next diff!!!
private final FlatViewGroup mFlatViewGroup;
DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
// lookups in o(1) instead of o(log n) - trade space for time
private final Map<Integer, DrawView> mDrawViewMap = new HashMap<>();
// When grandchildren are promoted, these can only be FlatViewGroups, but we need to handle the
// case that we clip subviews and don't promote grandchildren.
private final Map<Integer, View> mClippedSubviews = new HashMap<>();
protected final Rect mClippingRect = new Rect();
abstract boolean beforeRect(DrawView drawView);
abstract boolean afterRect(DrawView drawView);
/* package */ DirectionalClippingDrawCommandManager(
FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) {
mFlatViewGroup = flatViewGroup;
initialSetup(drawCommands);
}
private void initialSetup(DrawCommand[] drawCommands) {
mountDrawCommands(drawCommands);
updateClippingRect();
}
public void mountDrawCommands(DrawCommand[] drawCommands) {
mDrawCommands = drawCommands;
mDrawViewMap.clear();
for (DrawCommand drawCommand : mDrawCommands) {
if (drawCommand instanceof DrawView) {
DrawView drawView = (DrawView) drawCommand;
mDrawViewMap.put(drawView.reactTag, drawView);
}
}
}
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.containsKey(id);
}
private boolean isNotClipped(int id) {
return !mClippedSubviews.containsKey(id);
}
public void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach) {
for (int viewToAdd : viewsToAdd) {
if (viewToAdd > 0) {
// This view was not previously attached to this parent.
View view = viewResolver.getView(viewToAdd);
// ensureViewHasNoParent(view);
DrawView drawView = Assertions.assertNotNull(mDrawViewMap.get(viewToAdd));
drawView.mWasMounted = true;
if (animating(view) || withinBounds(drawView)) {
// 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.
DrawView drawView = Assertions.assertNotNull(mDrawViewMap.get(-viewToAdd));
View view = viewResolver.getView(drawView.reactTag);
// ensureViewHasNoParent(view);
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(drawView)) {
// 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.
static boolean animating(View view) {
Animation animation = view.getAnimation();
return animation != null && !animation.hasEnded();
}
// Return true if a DrawView is currently onscreen.
boolean withinBounds(DrawView drawView) {
return !(beforeRect(drawView) || afterRect(drawView));
}
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 index = 0;
boolean needsInvalidate = false;
for (DrawCommand drawCommand : mDrawCommands) {
if (drawCommand instanceof DrawView) {
DrawView drawView = (DrawView) drawCommand;
View view = mClippedSubviews.get(drawView.reactTag);
if (view == null) {
// Not clipped, visible
view = mFlatViewGroup.getChildAt(index++);
if (!animating(view) && !withinBounds(drawView)) {
// Now off the screen. Don't invalidate in this case, as the canvas should not be
// redrawn unless new elements are coming onscreen.
clip(drawView.reactTag, view);
mFlatViewGroup.removeViewsInLayout(--index, 1);
}
} else {
// Clipped, invisible. We obviously aren't animating here, as if we were then we would not
// have clipped in the first place.
if (withinBounds(drawView)) {
// Now on the screen. Invalidate as we have a new element to draw.
unclip(drawView.reactTag);
mFlatViewGroup.addViewInLayout(view, index++);
needsInvalidate = true;
}
}
}
}
return needsInvalidate;
}
public void getClippingRect(Rect outClippingRect) {
outClippingRect.set(mClippingRect);
}
public Collection<View> getDetachedViews() {
return mClippedSubviews.values();
}
public void draw(Canvas canvas) {
for (DrawCommand drawCommand : mDrawCommands) {
if (drawCommand instanceof DrawView) {
if (isNotClipped(((DrawView) drawCommand).reactTag)) {
drawCommand.draw(mFlatViewGroup, canvas);
}
// else, don't draw, and don't increment index
} else {
drawCommand.draw(mFlatViewGroup, canvas);
}
}
}
void debugDraw(Canvas canvas) {
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);
}
}
}
}

View File

@ -136,9 +136,9 @@ import android.view.ViewParent;
} }
} }
static DrawCommandManager getClippingInstance( static DrawCommandManager getVerticalClippingInstance(
FlatViewGroup flatViewGroup, FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) { DrawCommand[] drawCommands) {
return new ClippingDrawCommandManager(flatViewGroup, drawCommands); return new VerticalDrawCommandManager(flatViewGroup, drawCommands);
} }
} }

View File

@ -37,14 +37,15 @@ import android.graphics.RectF;
// the path to clip against if we're doing path clipping for rounded borders. // the path to clip against if we're doing path clipping for rounded borders.
@Nullable private Path mPath; @Nullable private Path mPath;
// These should only ever be set from within the DrawView, their only purpose is to prevent // These should only ever be set from within the DrawView, they serve to provide clipping bounds
// excessive rounding on the UI thread in FlatViewGroup, and they are left package protected to // for FlatViewGroups, which have strange clipping when it comes to overflow: visible. They are
// speed up direct access. For overflow visible, these are the adjusted bounds while taking // left package protected to speed up direct access. For overflow visible, these are the adjusted
// overflowing elements into account. // bounds while taking overflowing elements into account, other wise they are just the regular
/* package */ int mLogicalLeft; // bounds of the view.
/* package */ int mLogicalTop; /* package */ float mLogicalLeft;
/* package */ int mLogicalRight; /* package */ float mLogicalTop;
/* package */ int mLogicalBottom; /* package */ float mLogicalRight;
/* package */ float mLogicalBottom;
public DrawView(int reactTag) { public DrawView(int reactTag) {
this.reactTag = reactTag; this.reactTag = reactTag;
@ -62,10 +63,10 @@ import android.graphics.RectF;
float top, float top,
float right, float right,
float bottom, float bottom,
int logicalLeft, float logicalLeft,
int logicalTop, float logicalTop,
int logicalRight, float logicalRight,
int logicalBottom, float logicalBottom,
float clipLeft, float clipLeft,
float clipTop, float clipTop,
float clipRight, float clipRight,
@ -116,12 +117,12 @@ import android.graphics.RectF;
return drawView; return drawView;
} }
private boolean logicalBoundsMatch(int left, int top, int right, int bottom) { private boolean logicalBoundsMatch(float left, float top, float right, float bottom) {
return left == mLogicalLeft && top == mLogicalTop && return left == mLogicalLeft && top == mLogicalTop &&
right == mLogicalRight && bottom == mLogicalBottom; right == mLogicalRight && bottom == mLogicalBottom;
} }
private void setLogicalBounds(int left, int top, int right, int bottom) { private void setLogicalBounds(float left, float top, float right, float bottom) {
// Do rounding up front and off of the UI thread. // Do rounding up front and off of the UI thread.
mLogicalLeft = left; mLogicalLeft = left;
mLogicalTop = top; mLogicalTop = top;

View File

@ -506,10 +506,10 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
top, top,
right, right,
bottom, bottom,
Math.round(left + mLogicalOffset.left), left + mLogicalOffset.left,
Math.round(top + mLogicalOffset.top), top + mLogicalOffset.top,
Math.round(right + mLogicalOffset.right), right + mLogicalOffset.right,
Math.round(bottom + mLogicalOffset.bottom), bottom + mLogicalOffset.bottom,
clipLeft, clipLeft,
clipTop, clipTop,
clipRight, clipRight,
@ -547,4 +547,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
public boolean clipsSubviews() { public boolean clipsSubviews() {
return false; return false;
} }
public boolean isHorizontal() {
return false;
}
} }

View File

@ -876,10 +876,6 @@ import com.facebook.react.views.view.ReactClippingViewGroup;
} }
} }
/* package */ void detachView(int index) {
detachViewFromParent(index);
}
@Override @Override
public void getClippingRect(Rect outClippingRect) { public void getClippingRect(Rect outClippingRect) {
if (mDrawCommandManager == null) { if (mDrawCommandManager == null) {
@ -905,7 +901,7 @@ import com.facebook.react.views.view.ReactClippingViewGroup;
throw new RuntimeException( throw new RuntimeException(
"Trying to transition FlatViewGroup from clipping to non-clipping state"); "Trying to transition FlatViewGroup from clipping to non-clipping state");
} }
mDrawCommandManager = DrawCommandManager.getClippingInstance(this, mDrawCommands); mDrawCommandManager = DrawCommandManager.getVerticalClippingInstance(this, mDrawCommands);
mDrawCommands = DrawCommand.EMPTY_ARRAY; mDrawCommands = DrawCommand.EMPTY_ARRAY;
// We don't need an invalidate here because this can't cause new views to come onscreen, since // We don't need an invalidate here because this can't cause new views to come onscreen, since
// everything was unclipped. // everything was unclipped.

View File

@ -1,33 +0,0 @@
/**
* 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;
/**
* {@link DrawCommandManager} with horizontal clipping (The view scrolls left and right).
*/
/* package */ final class HorizontalClippingDrawCommandManager extends
DirectionalClippingDrawCommandManager {
/* package */ HorizontalClippingDrawCommandManager(
FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) {
super(flatViewGroup, drawCommands);
}
@Override
public boolean beforeRect(DrawView drawView) {
return drawView.mLogicalRight <= mClippingRect.left;
}
@Override
public boolean afterRect(DrawView drawView) {
return drawView.mLogicalLeft >= mClippingRect.right;
}
}

View File

@ -0,0 +1,106 @@
/**
* 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 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;
}
// These should never be called from the UI thread, as the reason they exist is to do work off the
// UI thread.
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].mRight);
maxBottom[i] = last;
}
for (int i = regions.length - 1; i >= 0; i--) {
last = Math.min(last, regions[i].mLeft);
minTop[i] = last;
}
}
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.mLogicalRight);
} else {
last = Math.max(last, commands[i].getRight());
}
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]).mLogicalLeft);
} else {
last = Math.min(last, commands[i].getLeft());
}
minTop[i] = last;
}
}
}

View File

@ -351,32 +351,12 @@ import com.facebook.react.uimanager.events.EventDispatcher;
commandMaxBottom = new float[drawCommands.length]; commandMaxBottom = new float[drawCommands.length];
commandMinTop = new float[drawCommands.length]; commandMinTop = new float[drawCommands.length];
float last = 0; if (node.isHorizontal()) {
// Loop through the DrawCommands, keeping track of the maximum y we've seen if we only HorizontalDrawCommandManager
// iterated through items up to this position .fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
for (int i = 0; i < drawCommands.length; i++) { } else {
if (drawCommands[i] instanceof DrawView) { VerticalDrawCommandManager
DrawView drawView = (DrawView) drawCommands[i]; .fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
// 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, drawCommands[i].getBottom());
}
commandMaxBottom[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 the DrawCommands backwards, keeping track of the minimum y we've seen at
// this position
for (int i = drawCommands.length - 1; i >= 0; i--) {
if (drawCommands[i] instanceof DrawView) {
last = Math.min(last, ((DrawView) drawCommands[i]).mLogicalTop);
} else {
last = Math.min(last, drawCommands[i].getTop());
}
commandMinTop[i] = last;
} }
} }
float[] regionMaxBottom = EMPTY_FLOAT_ARRAY; float[] regionMaxBottom = EMPTY_FLOAT_ARRAY;
@ -385,14 +365,12 @@ import com.facebook.react.uimanager.events.EventDispatcher;
regionMaxBottom = new float[nodeRegions.length]; regionMaxBottom = new float[nodeRegions.length];
regionMinTop = new float[nodeRegions.length]; regionMinTop = new float[nodeRegions.length];
float last = 0; if (node.isHorizontal()) {
for (int i = 0; i < nodeRegions.length; i++) { HorizontalDrawCommandManager
last = Math.max(last, nodeRegions[i].mBottom); .fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
regionMaxBottom[i] = last; } else {
} VerticalDrawCommandManager
for (int i = nodeRegions.length - 1; i >= 0; i--) { .fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
last = Math.min(last, nodeRegions[i].mTop);
regionMinTop[i] = last;
} }
} }

View File

@ -1,33 +0,0 @@
/**
* 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;
/**
* {@link DrawCommandManager} with vertical clipping (The view scrolls up and down).
*/
/* package */ final class VerticalClippingDrawCommandManager extends
DirectionalClippingDrawCommandManager {
/* package */ VerticalClippingDrawCommandManager(
FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) {
super(flatViewGroup, drawCommands);
}
@Override
boolean beforeRect(DrawView drawView) {
return drawView.mLogicalBottom <= mClippingRect.top;
}
@Override
boolean afterRect(DrawView drawView) {
return drawView.mLogicalTop >= mClippingRect.bottom;
}
}

View File

@ -0,0 +1,108 @@
/**
* 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 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;
}
// These should never be called from the UI thread, as the reason they exist is to do work off
// the UI thread.
public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxBot, float[] minTop) {
float last = 0;
for (int i = 0; i < regions.length; i++) {
last = Math.max(last, regions[i].mBottom);
maxBot[i] = last;
}
for (int i = regions.length - 1; i >= 0; i--) {
last = Math.min(last, regions[i].mTop);
minTop[i] = last;
}
}
// These should never be called from the UI thread, as the reason they exist is to do work off
// the UI thread.
public static void fillMaxMinArrays(
DrawCommand[] commands,
float[] maxBot,
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());
}
maxBot[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;
}
}
}