mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-05 09:29:07 +08:00
Support rounded clipping
Summary: Support rounded clipping in Nodes. Before, if a view had a radius and had overflow of hidden, its children could still draw outside of it (specifically, in the area between the rounded rect and square rect) - this is due to the fact that clipping is, by default, rectangular. This patch supports this type of rounded clipping. Differential Revision: D3634861
This commit is contained in:
@@ -75,7 +75,7 @@ import android.graphics.Color;
|
||||
return mClipBottom;
|
||||
}
|
||||
|
||||
protected final void applyClipping(Canvas canvas) {
|
||||
protected void applyClipping(Canvas canvas) {
|
||||
canvas.clipRect(mClipLeft, mClipTop, mClipRight, mClipBottom);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,16 @@
|
||||
|
||||
package com.facebook.react.flat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
|
||||
/* package */ final class DrawView extends AbstractDrawCommand {
|
||||
// the minimum rounded clipping value before we actually do rounded clipping
|
||||
/* package */ static final float MINIMUM_ROUNDED_CLIPPING_VALUE = 0.5f;
|
||||
private final RectF TMP_RECT = new RectF();
|
||||
|
||||
/* package */ final int reactTag;
|
||||
// Indicates whether this DrawView has been previously mounted to a clipping FlatViewGroup. This
|
||||
@@ -22,6 +29,13 @@ import android.graphics.Canvas;
|
||||
// quickest way to create unreproducible super bugs.
|
||||
/* package */ boolean mWasMounted;
|
||||
|
||||
// the clipping radius - if this is greater than MINIMUM_ROUNDED_CLIPPING_VALUE, we clip using
|
||||
// a rounded path, otherwise we clip in a rectangular fashion.
|
||||
private float mClipRadius;
|
||||
|
||||
// the path to clip against if we're doing path clipping for rounded borders.
|
||||
@Nullable private Path mPath;
|
||||
|
||||
public DrawView(int reactTag) {
|
||||
this.reactTag = reactTag;
|
||||
}
|
||||
@@ -41,10 +55,39 @@ import android.graphics.Canvas;
|
||||
float clipLeft,
|
||||
float clipTop,
|
||||
float clipRight,
|
||||
float clipBottom) {
|
||||
float clipBottom,
|
||||
float clipRadius) {
|
||||
DrawView drawView = (DrawView)
|
||||
updateBoundsAndFreeze(left, top, right, bottom, clipLeft, clipTop, clipRight, clipBottom);
|
||||
updateBoundsAndFreeze(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
boolean clipRadiusChanged = Math.abs(mClipRadius - clipRadius) > 0.001f;
|
||||
if (clipRadiusChanged && drawView == this) {
|
||||
// everything matches except the clip radius, so we clone the old one so that we can update
|
||||
// the clip radius in the block below.
|
||||
try {
|
||||
drawView = (DrawView) clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// This should not happen since AbstractDrawCommand implements Cloneable
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (drawView != this) {
|
||||
drawView.mClipRadius = clipRadius;
|
||||
if (clipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
// update the path that we'll clip based on
|
||||
updateClipPath(drawView);
|
||||
} else {
|
||||
drawView.mPath = null;
|
||||
}
|
||||
|
||||
// It is very important that we unset this, as our spec is that newly created DrawViews are
|
||||
// handled differently by the FlatViewGroup. This is needed because updateBoundsAndFreeze
|
||||
// uses .clone(), so we maintain the previous state.
|
||||
@@ -56,7 +99,7 @@ import android.graphics.Canvas;
|
||||
@Override
|
||||
public void draw(FlatViewGroup parent, Canvas canvas) {
|
||||
onPreDraw(parent, canvas);
|
||||
if (mNeedsClipping) {
|
||||
if (mNeedsClipping || mClipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
canvas.save(Canvas.CLIP_SAVE_FLAG);
|
||||
applyClipping(canvas);
|
||||
parent.drawNextChild(canvas);
|
||||
@@ -66,6 +109,43 @@ import android.graphics.Canvas;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the path with which we'll clip this view
|
||||
* @param drawView the drawView to update the path for
|
||||
*/
|
||||
private void updateClipPath(DrawView drawView) {
|
||||
// make or reset the path
|
||||
if (drawView.mPath == null) {
|
||||
drawView.mPath = new Path();
|
||||
} else {
|
||||
drawView.mPath.reset();
|
||||
}
|
||||
|
||||
TMP_RECT.set(
|
||||
getLeft(),
|
||||
getTop(),
|
||||
getRight(),
|
||||
getBottom());
|
||||
|
||||
// set the path
|
||||
drawView.mPath.addRoundRect(
|
||||
TMP_RECT,
|
||||
drawView.mClipRadius,
|
||||
drawView.mClipRadius,
|
||||
Path.Direction.CW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyClipping(Canvas canvas) {
|
||||
// only clip using a path if our radius is greater than some minimum threshold, because
|
||||
// clipPath is more expensive than clipRect.
|
||||
if (mClipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
canvas.clipPath(mPath);
|
||||
} else {
|
||||
super.applyClipping(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
// no op as we override draw.
|
||||
|
||||
@@ -60,7 +60,6 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
private boolean mBackingViewIsCreated;
|
||||
private @Nullable DrawView mDrawView;
|
||||
private @Nullable DrawBackgroundColor mDrawBackground;
|
||||
private boolean mClipToBounds = false;
|
||||
private boolean mIsUpdated = true;
|
||||
private boolean mForceMountChildrenToView;
|
||||
private float mClipLeft;
|
||||
@@ -82,6 +81,10 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
private int mLayoutWidth;
|
||||
private int mLayoutHeight;
|
||||
|
||||
// clip radius
|
||||
float mClipRadius;
|
||||
boolean mClipToBounds = false;
|
||||
|
||||
/* package */ void handleUpdateProperties(ReactStylesDiffMap styles) {
|
||||
if (!mountsToView()) {
|
||||
// Make sure we mount this FlatShadowNode to a View if any of these properties are present.
|
||||
@@ -152,6 +155,11 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
mClipToBounds = "hidden".equals(overflow);
|
||||
if (mClipToBounds) {
|
||||
mOverflowsContainer = false;
|
||||
if (mClipRadius > DrawView.MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
// mount to a view if we are overflow: hidden and are clipping, so that we can do one
|
||||
// clipPath to clip all the children of this node (both DrawCommands and Views).
|
||||
forceMountToView();
|
||||
}
|
||||
} else {
|
||||
updateOverflowsContainer();
|
||||
}
|
||||
@@ -483,6 +491,9 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
// DrawView anyway, as reactTag is final, and our DrawView instance is the static copy.
|
||||
mDrawView = new DrawView(getReactTag());
|
||||
}
|
||||
|
||||
// avoid path clipping if overflow: visible
|
||||
float clipRadius = mClipToBounds ? mClipRadius : 0.0f;
|
||||
// We have the correct react tag, but we may need a new copy with updated bounds. If the bounds
|
||||
// match or were never set, the same view is returned.
|
||||
mDrawView = mDrawView.collectDrawView(
|
||||
@@ -493,7 +504,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper;
|
||||
clipLeft,
|
||||
clipTop,
|
||||
clipRight,
|
||||
clipBottom);
|
||||
clipBottom,
|
||||
clipRadius);
|
||||
return mDrawView;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,12 @@ import com.facebook.react.uimanager.annotations.ReactPropGroup;
|
||||
|
||||
@ReactProp(name = "borderRadius")
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
mClipRadius = borderRadius;
|
||||
if (mClipToBounds && borderRadius > DrawView.MINIMUM_ROUNDED_CLIPPING_VALUE) {
|
||||
// mount to a view if we are overflow: hidden and are clipping, so that we can do one
|
||||
// clipPath to clip all the children of this node (both DrawCommands and Views).
|
||||
forceMountToView();
|
||||
}
|
||||
getMutableBorder().setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user