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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
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
* 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 DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private float[] mCommandMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
private float[] mCommandMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
protected float[] mCommandMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
protected float[] mCommandMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
private float[] mRegionMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
private float[] mRegionMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
protected float[] mRegionMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
protected float[] mRegionMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
// Onscreen bounds of draw command array.
private int mStart;
@ -51,7 +50,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
// Map of views that are currently clipped.
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
// 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();
}
/**
* @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
public void mountDrawCommands(
DrawCommand[] drawCommands,
@ -86,20 +112,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
mCommandMinTop = minTop;
mDrawViewIndexMap = drawViewIndexMap;
if (mClippingRect.bottom != mClippingRect.top) {
mStart = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.top);
if (mStart < 0) {
// 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;
}
mStart = commandStartIndex();
mStop = commandStopIndex(mStart);
if (!willMountViews) {
// 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
@ -118,18 +132,14 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
@Override
public @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY) {
int i = Arrays.binarySearch(mRegionMinTop, touchY + 0.0001f);
if (i < 0) {
// We don't care whether we matched or not, but positive indices are helpful.
i = ~i;
}
int i = regionStopIndex(touchX, touchY);
while (i-- > 0) {
NodeRegion nodeRegion = mNodeRegions[i];
if (!nodeRegion.mIsVirtual) {
// only interested in virtual nodes
continue;
}
if (mRegionMaxBottom[i] < touchY) {
if (regionAboveTouch(i, touchX, touchY)) {
break;
}
if (nodeRegion.withinBounds(touchX, touchY)) {
@ -142,14 +152,10 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
@Override
public @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY) {
int i = Arrays.binarySearch(mRegionMinTop, touchY + 0.0001f);
if (i < 0) {
// We don't care whether we matched or not, but positive indices are helpful.
i = ~i;
}
int i = regionStopIndex(touchX, touchY);
while (i-- > 0) {
NodeRegion nodeRegion = mNodeRegions[i];
if (mRegionMaxBottom[i] < touchY) {
if (regionAboveTouch(i, touchX, touchY)) {
break;
}
if (nodeRegion.withinBounds(touchX, touchY)) {
@ -250,7 +256,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
}
// Returns true if a view is currently animating.
static boolean animating(View view) {
private static boolean animating(View view) {
Animation animation = view.getAnimation();
return animation != null && !animation.hasEnded();
}
@ -269,22 +275,11 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
return false;
}
int start = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.top);
if (start < 0) {
// 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;
}
int start = commandStartIndex();
int stop = commandStopIndex(start);
if (mStart <= start && stop <= mStop) {
// We would only be removing children, don't invalidate and don't bother changing the
// attached children.
return false;
}
@ -382,21 +377,24 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
int commandIndex = mStart;
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++) {
// 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());
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) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
}
// We are now out of commands to draw, so we can just draw the remaining attached children.
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas);
while (++i != size) {
viewIndex = mDrawViewIndexMap.get(mFlatViewGroup.getChildAt(i).getId());
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas);
}
// Everything is drawn, lets get out of here.
return;
// We are now out of commands to draw, so we could just draw the remaining attached
// children, but the for loop logic will draw the rest anyway.
} 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) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
}
@ -406,7 +404,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
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) {
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,
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.
@Nullable private Path mPath;
// These should only ever be set from within the DrawView, their only purpose is to prevent
// excessive rounding on the UI thread in FlatViewGroup, and they are left package protected to
// speed up direct access. For overflow visible, these are the adjusted bounds while taking
// overflowing elements into account.
/* package */ int mLogicalLeft;
/* package */ int mLogicalTop;
/* package */ int mLogicalRight;
/* package */ int mLogicalBottom;
// These should only ever be set from within the DrawView, they serve to provide clipping bounds
// for FlatViewGroups, which have strange clipping when it comes to overflow: visible. They are
// left package protected to speed up direct access. For overflow visible, these are the adjusted
// bounds while taking overflowing elements into account, other wise they are just the regular
// bounds of the view.
/* package */ float mLogicalLeft;
/* package */ float mLogicalTop;
/* package */ float mLogicalRight;
/* package */ float mLogicalBottom;
public DrawView(int reactTag) {
this.reactTag = reactTag;
@ -62,10 +63,10 @@ import android.graphics.RectF;
float top,
float right,
float bottom,
int logicalLeft,
int logicalTop,
int logicalRight,
int logicalBottom,
float logicalLeft,
float logicalTop,
float logicalRight,
float logicalBottom,
float clipLeft,
float clipTop,
float clipRight,
@ -116,12 +117,12 @@ import android.graphics.RectF;
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 &&
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.
mLogicalLeft = left;
mLogicalTop = top;

View File

@ -506,10 +506,10 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
top,
right,
bottom,
Math.round(left + mLogicalOffset.left),
Math.round(top + mLogicalOffset.top),
Math.round(right + mLogicalOffset.right),
Math.round(bottom + mLogicalOffset.bottom),
left + mLogicalOffset.left,
top + mLogicalOffset.top,
right + mLogicalOffset.right,
bottom + mLogicalOffset.bottom,
clipLeft,
clipTop,
clipRight,
@ -547,4 +547,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
public boolean clipsSubviews() {
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
public void getClippingRect(Rect outClippingRect) {
if (mDrawCommandManager == null) {
@ -905,7 +901,7 @@ import com.facebook.react.views.view.ReactClippingViewGroup;
throw new RuntimeException(
"Trying to transition FlatViewGroup from clipping to non-clipping state");
}
mDrawCommandManager = DrawCommandManager.getClippingInstance(this, mDrawCommands);
mDrawCommandManager = DrawCommandManager.getVerticalClippingInstance(this, mDrawCommands);
mDrawCommands = DrawCommand.EMPTY_ARRAY;
// We don't need an invalidate here because this can't cause new views to come onscreen, since
// 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];
commandMinTop = new float[drawCommands.length];
float last = 0;
// Loop through the DrawCommands, keeping track of the maximum y we've seen if we only
// iterated through items up to this position
for (int i = 0; i < drawCommands.length; i++) {
if (drawCommands[i] instanceof DrawView) {
DrawView drawView = (DrawView) drawCommands[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, 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;
if (node.isHorizontal()) {
HorizontalDrawCommandManager
.fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
} else {
VerticalDrawCommandManager
.fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
}
}
float[] regionMaxBottom = EMPTY_FLOAT_ARRAY;
@ -385,14 +365,12 @@ import com.facebook.react.uimanager.events.EventDispatcher;
regionMaxBottom = new float[nodeRegions.length];
regionMinTop = new float[nodeRegions.length];
float last = 0;
for (int i = 0; i < nodeRegions.length; i++) {
last = Math.max(last, nodeRegions[i].mBottom);
regionMaxBottom[i] = last;
}
for (int i = nodeRegions.length - 1; i >= 0; i--) {
last = Math.min(last, nodeRegions[i].mTop);
regionMinTop[i] = last;
if (node.isHorizontal()) {
HorizontalDrawCommandManager
.fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
} else {
VerticalDrawCommandManager
.fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
}
}

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;
}
}
}