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:
parent
192c99a4f6
commit
a4c4a88e27
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue