Skip to content

Commit

Permalink
Set rounded rectangle mask on ripples
Browse files Browse the repository at this point in the history
This commit fixes an issue where ripple touch feedback extends beyond
the border radius of a view.

It achieves this by adding a mask to the RippleDrawable background,
collecting that information from two new methods on ReactViewGroup:

1. getBorderRadiusMask() returns a drawable rounded rectangle matching
the view's border radius properties
2. getBorderRadius() produces a float[] with the border radius
information required to build a RoundedRectShape in
getBorderRadiusMask()

Additionally, this commit updates setBorderRadius in ReactViewManager
to re-apply the background whenever it is set, which is necessary to
update the mask on the RippleDrawable background image as the border
radius changes.

Related issues:
#6480
  • Loading branch information
nhunzaker committed Jul 10, 2019
1 parent 7dc0d4b commit 6dfe068
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
Expand All @@ -30,7 +28,9 @@ public class ReactDrawableHelper {

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static Drawable createDrawableFromJSDescription(
Context context, ReadableMap drawableDescriptionDict) {
ReactViewGroup view,
ReadableMap drawableDescriptionDict) {
Context context = view.getContext();
String type = drawableDescriptionDict.getString("type");
if ("ThemeAttrAndroid".equals(type)) {
String attr = drawableDescriptionDict.getString("attribute");
Expand Down Expand Up @@ -75,7 +75,7 @@ public static Drawable createDrawableFromJSDescription(
if (!drawableDescriptionDict.hasKey("borderless")
|| drawableDescriptionDict.isNull("borderless")
|| !drawableDescriptionDict.getBoolean("borderless")) {
mask = new ColorDrawable(Color.WHITE);
mask = view.getBorderRadiusMask();
}
ColorStateList colorStateList =
new ColorStateList(new int[][] {new int[] {}}, new int[] {color});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;
Expand Down Expand Up @@ -282,10 +284,46 @@ public void setBorderRadius(float borderRadius, int position) {
}
}

public float[] getBorderRadius() {
final float borderRadius = mReactBackgroundDrawable.getFullBorderRadius();
float topLeftBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_LEFT);
float topRightBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_RIGHT);
float bottomLeftBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_LEFT);
float bottomRightBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_RIGHT);

return new float[] {
topLeftBorderRadius,
topLeftBorderRadius,
topRightBorderRadius,
topRightBorderRadius,
bottomRightBorderRadius,
bottomRightBorderRadius,
bottomLeftBorderRadius,
bottomLeftBorderRadius
};
}

public void setBorderStyle(@Nullable String style) {
getOrCreateReactViewBackground().setBorderStyle(style);
}

public Drawable getBorderRadiusMask() {
RoundRectShape r = new RoundRectShape(getBorderRadius(), null, null);

ShapeDrawable shapeDrawable = new ShapeDrawable(r);
shapeDrawable.getPaint().setColor(Color.WHITE);

return shapeDrawable;
}

@Override
public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
if (removeClippedSubviews == mRemoveClippedSubviews) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class ReactViewManager extends ViewGroupManager<ReactViewGroup> {
private static final int CMD_SET_PRESSED = 2;
private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate";

private ReadableMap mBg = null;
private ReadableMap mFg = null;

@ReactProp(name = "accessible")
public void setAccessible(ReactViewGroup view, boolean accessible) {
view.setFocusable(accessible);
Expand Down Expand Up @@ -118,6 +121,14 @@ public void setBorderRadius(ReactViewGroup view, int index, float borderRadius)
} else {
view.setBorderRadius(borderRadius, index - 1);
}

if (mBg != null) {
setNativeBackground(view, mBg);
}

if (mFg != null) {
setNativeForeground(view, mFg);
}
}

@ReactProp(name = "borderStyle")
Expand Down Expand Up @@ -158,19 +169,18 @@ public void setPointerEvents(ReactViewGroup view, @Nullable String pointerEvents

@ReactProp(name = "nativeBackgroundAndroid")
public void setNativeBackground(ReactViewGroup view, @Nullable ReadableMap bg) {
view.setTranslucentBackgroundDrawable(
bg == null
? null
: ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
mBg = bg;
view.setTranslucentBackgroundDrawable(bg == null ?
null : ReactDrawableHelper.createDrawableFromJSDescription(view, bg));
}

@TargetApi(Build.VERSION_CODES.M)
@ReactProp(name = "nativeForegroundAndroid")
public void setNativeForeground(ReactViewGroup view, @Nullable ReadableMap fg) {
view.setForeground(
fg == null
? null
: ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), fg));
mFg = fg;
view.setForeground(fg == null
? null
: ReactDrawableHelper.createDrawableFromJSDescription(view, fg));
}

@ReactProp(
Expand Down

0 comments on commit 6dfe068

Please sign in to comment.