Skip to content

Commit

Permalink
[Airbnb] Adds corner-radius support to ripples created in TouchableNa…
Browse files Browse the repository at this point in the history
…tiveFeedback. (#10)

This follows the same approach as b59f99b
but fixes a bug in ReactViewGroup#refreshTranslucentBackgroundDrawable
where a view that has a borderRadius, but no nativeBackground would
not be rendered due to a missing condition case.
  • Loading branch information
Ben Schwab authored and Gabriel Peal committed Jul 23, 2017
1 parent 647b0c9 commit 63bd3ba
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 36 deletions.
1 change: 1 addition & 0 deletions RNTester/js/TouchableExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ exports.examples = [
backgroundColor: 'rgb(180, 64, 119)',
width: 200,
height: 100,
borderRadius: 20,
transform: [{scale: mScale}]
};
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
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.PaintDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.util.TypedValue;
Expand All @@ -23,6 +23,8 @@
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.uimanager.ViewProps;

import javax.annotation.Nullable;

/**
* Utility class that helps with converting android drawable description used in JS to an actual
* instance of {@link Drawable}.
Expand All @@ -31,9 +33,16 @@ public class ReactDrawableHelper {

private static final TypedValue sResolveOutValue = new TypedValue();

public static Drawable createDrawableFromJSDescription(
Context context,
ReadableMap drawableDescriptionDict) {
return createDrawableFromJSDescription(context, drawableDescriptionDict, null);
}

public static Drawable createDrawableFromJSDescription(
Context context,
ReadableMap drawableDescriptionDict) {
ReadableMap drawableDescriptionDict,
@Nullable float[] cornerRadii) {
String type = drawableDescriptionDict.getString("type");
if ("ThemeAttrAndroid".equals(type)) {
String attr = drawableDescriptionDict.getString("attribute");
Expand Down Expand Up @@ -75,11 +84,14 @@ public static Drawable createDrawableFromJSDescription(
"couldn't be resolved into a drawable");
}
}
Drawable mask = null;
PaintDrawable mask = null;
if (!drawableDescriptionDict.hasKey("borderless") ||
drawableDescriptionDict.isNull("borderless") ||
!drawableDescriptionDict.getBoolean("borderless")) {
mask = new ColorDrawable(Color.WHITE);
drawableDescriptionDict.isNull("borderless") ||
!drawableDescriptionDict.getBoolean("borderless")) {
mask = new PaintDrawable(Color.WHITE);
if (cornerRadii != null) {
mask.setCornerRadii(cornerRadii);
}
}
ColorStateList colorStateList = new ColorStateList(
new int[][] {new int[]{}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,25 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) {
}
}

/* package */ float[] getBorderRadii() {
float defaultBorderRadius = !YogaConstants.isUndefined(mBorderRadius) ? mBorderRadius : 0;
float topLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[0]) ? mBorderCornerRadii[0] : defaultBorderRadius;
float topRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[1]) ? mBorderCornerRadii[1] : defaultBorderRadius;
float bottomRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius;
float bottomLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius;

return new float[] {
topLeftRadius,
topLeftRadius,
topRightRadius,
topRightRadius,
bottomRightRadius,
bottomRightRadius,
bottomLeftRadius,
bottomLeftRadius
};
}

private void updatePath() {
if (!mNeedUpdatePathForBorderRadius) {
return;
Expand All @@ -282,25 +301,12 @@ private void updatePath() {
mTempRectForBorderRadius.inset(fullBorderWidth * 0.5f, fullBorderWidth * 0.5f);
}

float defaultBorderRadius = !YogaConstants.isUndefined(mBorderRadius) ? mBorderRadius : 0;
float topLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[0]) ? mBorderCornerRadii[0] : defaultBorderRadius;
float topRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[1]) ? mBorderCornerRadii[1] : defaultBorderRadius;
float bottomRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius;
float bottomLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius;
float[] borderRadii = getBorderRadii();

mPathForBorderRadius.addRoundRect(
mTempRectForBorderRadius,
new float[] {
topLeftRadius,
topLeftRadius,
topRightRadius,
topRightRadius,
bottomRightRadius,
bottomRightRadius,
bottomLeftRadius,
bottomLeftRadius
},
Path.Direction.CW);
mTempRectForBorderRadius,
borderRadii,
Path.Direction.CW);

float extraRadiusForOutline = 0;

Expand All @@ -311,14 +317,14 @@ private void updatePath() {
mPathForBorderRadiusOutline.addRoundRect(
mTempRectForBorderRadiusOutline,
new float[] {
topLeftRadius + extraRadiusForOutline,
topLeftRadius + extraRadiusForOutline,
topRightRadius + extraRadiusForOutline,
topRightRadius + extraRadiusForOutline,
bottomRightRadius + extraRadiusForOutline,
bottomRightRadius + extraRadiusForOutline,
bottomLeftRadius + extraRadiusForOutline,
bottomLeftRadius + extraRadiusForOutline
borderRadii[0] + extraRadiusForOutline,
borderRadii[1] + extraRadiusForOutline,
borderRadii[2] + extraRadiusForOutline,
borderRadii[3] + extraRadiusForOutline,
borderRadii[4] + extraRadiusForOutline,
borderRadii[5] + extraRadiusForOutline,
borderRadii[6] + extraRadiusForOutline,
borderRadii[7] + extraRadiusForOutline
},
Path.Direction.CW);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

package com.facebook.react.views.view;

import javax.annotation.Nullable;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
Expand All @@ -22,6 +20,7 @@
import android.view.ViewGroup;

import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.touch.ReactHitSlopView;
import com.facebook.react.touch.ReactInterceptingViewGroup;
Expand All @@ -34,6 +33,8 @@
import com.facebook.react.uimanager.ReactZIndexedViewGroup;
import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;

import javax.annotation.Nullable;

/**
* Backing for a React View. Has support for borders, but since borders aren't common, lazy
* initializes most of the storage needed for them.
Expand Down Expand Up @@ -100,6 +101,7 @@ public void onLayoutChange(
private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
private boolean mNeedsOffscreenAlphaCompositing = false;
private final ViewGroupDrawingOrderHelper mDrawingOrderHelper;
private @Nullable ReadableMap mNativeBackground;

public ReactViewGroup(Context context) {
super(context);
Expand Down Expand Up @@ -142,16 +144,33 @@ public void setBackground(Drawable drawable) {
"This method is not supported for ReactViewGroup instances");
}

public void setTranslucentBackgroundDrawable(@Nullable Drawable background) {
public void setNativeBackground(@Nullable ReadableMap nativeBackground) {
mNativeBackground = nativeBackground;
refreshTranslucentBackgroundDrawable();
}

public void refreshTranslucentBackgroundDrawable() {
// it's required to call setBackground to null, as in some of the cases we may set new
// background to be a layer drawable that contains a drawable that has been previously setup
// as a background previously. This will not work correctly as the drawable callback logic is
// messed up in AOSP

Drawable background = null;
if (mNativeBackground != null) {
float[] cornerRadii = null;
if (mReactBackgroundDrawable != null) {
cornerRadii = mReactBackgroundDrawable.getBorderRadii();
}
background = ReactDrawableHelper.createDrawableFromJSDescription(getContext(), mNativeBackground, cornerRadii);
}

super.setBackground(null);
if (mReactBackgroundDrawable != null && background != null) {
LayerDrawable layerDrawable =
new LayerDrawable(new Drawable[] {mReactBackgroundDrawable, background});
super.setBackground(layerDrawable);
} else if (mReactBackgroundDrawable != null && background == null) {
super.setBackground(mReactBackgroundDrawable);
} else if (background != null) {
super.setBackground(background);
}
Expand Down Expand Up @@ -215,10 +234,12 @@ public void setBorderColor(int position, float rgb, float alpha) {

public void setBorderRadius(float borderRadius) {
getOrCreateReactViewBackground().setRadius(borderRadius);
refreshTranslucentBackgroundDrawable();
}

public void setBorderRadius(float borderRadius, int position) {
getOrCreateReactViewBackground().setRadius(borderRadius, position);
refreshTranslucentBackgroundDrawable();
}

public void setBorderStyle(@Nullable String style) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ 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));
view.setNativeBackground(bg);
}

@TargetApi(Build.VERSION_CODES.M)
Expand Down

0 comments on commit 63bd3ba

Please sign in to comment.