Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TouchableNativeFeedback's ripples aren't affected by borderRadius on Android 9 #6480

Closed
jeffchienzabinet opened this issue Mar 16, 2016 · 50 comments

Comments

@jeffchienzabinet
Copy link
Contributor

This is a bit of a problem when creating buttons with borderRadius and TouchableNativeFeedback, and the ripples extend out. I'm not an expert but I suppose setNativeBackground needs to get the corner radius when making the mask for the RippleDrawable and setBorderRadius may need to remake the background drawable somehow.

@jonathanpalma
Copy link

Attached the following images to represent graphically what is proposed

before
after

Images were taken from Michael Evans Blog - Android Ripples With Rounded Corners

@antoinerousseau
Copy link
Contributor

It's supposed to be implemented in #6515 but I can't make it work :/

@antoinerousseau
Copy link
Contributor

I realize that PR got reverted

@reyesr
Copy link

reyesr commented Jul 25, 2016

FWIW, as of 0.30, the ripple effect works with respect to the borderRadius of its parent , but it requires its borderless argument to be set to true.
Eg:

<View style={{borderRadius: 24, backgroundColor: '#F66', width: 48,height: 48}}> <TouchableNativeFeedback background={TouchableNativeFeedback.Ripple('#AAF', true)}> [snip] </TouchableNativeFeedback> </View>
If this is the intented effect, it should probably be made explicit, and added to the documentation. It it's a side effect (no pun intented), #6515 still makes sense.

@tituswoo
Copy link

@reyesr OMG thank you so much for this! 👍

@jonathanpalma
Copy link

@reyesr IT WORKS!!! Thank you so much buddy!! 🙌

@ericvicenti
Copy link
Contributor

Looks like this is resolved. Feel free to open a new issue if you're still having troubles with it

@satya164
Copy link
Contributor

Re-opening since this issue has re-surfaced on Android P.

@facebook facebook unlocked this conversation Aug 20, 2018
@satya164 satya164 removed the Resolution: Locked This issue was locked by the bot. label Aug 20, 2018
@ferrannp
Copy link
Contributor

Just to illustrate from Android P:

screenshot_20180820-124834_2

@lalosh
Copy link

lalosh commented Sep 1, 2018

@reyesr thank you..yet it worked for me after I added a View inside TochableNativeFeedback with the same parent style .. like:

        <View style={{ borderRadius: 20, backgroundColor: '#2f2', width: 80, height: 80 }}>
            <TouchableNativeFeedback background={TouchableNativeFeedback.Ripple('#AAF', true)}>
                <View style={{ borderRadius: 20, backgroundColor: '#2f2', width: 80, height: 80 }}>
                    <Text> test button </Text>
                </View>
             </TouchableNativeFeedback> 
         </View>

@hramos hramos added the Platform: Android Android applications. label Sep 25, 2018
@hramos hramos changed the title [Android] TouchableNativeFeedback's ripples aren't affected by borderRadius on Android 9 TouchableNativeFeedback's ripples aren't affected by borderRadius on Android 9 Sep 25, 2018
@yaronlevi
Copy link

0.55 + Android Pie = The ripple is not affected by the borderRadius.
Tried all the variations (View from outside, View from inside, both).

osdnk pushed a commit to osdnk/react-native that referenced this issue Mar 9, 2020
Summary:
motivation: there are cases where one'd like to control the radius of the ripple effect that's present on TouchableNativeFeedback  - in my case, I want to make sure that both icons and text have the same ripple appearance, but that's currently not possible as far as I can tell.

Currently (afaik) the only way to set  (upper) ripple limits is by specifying width, height and border radius ( + `overflow: hidden`), and this works well for icons which can usually be bounded by a square, but not for text which can have rectangular shape.

This PR adds `rippleRadius` parameter to `SelectableBackground()`, `SelectableBackgroundBorderless()` and `Ripple()` static functions present on `TouchableNativeFeedback`. It can make the ripple smaller but also larger. The result looks like this:

added to RNTester:

![SVID_20200219_182027_1](https://user-images.githubusercontent.com/1566403/74858131-147ff380-5345-11ea-8a9e-2730b79eec38.gif)

difference from the other ripples:

![SVID_20200209_110918_1](https://user-images.githubusercontent.com/1566403/74109152-4513a080-4b81-11ea-8ec3-bb5862c57244.gif)

I'm ofc open to changing the api if needed, but I'm not sure there's much space for manoeuvring. While I was at it, I did a slight refactor of the class into several smaller, more focused methods.

It's possible that in some cases, this might help to work around this issue facebook#6480.

## Changelog

[Android] [Added] - allow setting custom ripple radius on TouchableNativeFeedback
Pull Request resolved: facebook#28009

Test Plan: I tested this locally using RNTester

Reviewed By: TheSavior

Differential Revision: D20004509

Pulled By: mdvacca

fbshipit-source-id: 10de1754d54c17878f36a3859705c1188f15c2a2
@stale
Copy link

stale bot commented May 6, 2020

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label May 6, 2020
@ferrannp ferrannp removed the Stale There has been a lack of activity on this issue and it may be closed soon. label May 6, 2020
@likrot
Copy link

likrot commented Jul 7, 2020

FWIW, as of 0.30, the ripple effect works with respect to the borderRadius of its parent , but it requires its borderless argument to be set to true.
Eg:
<View style={{borderRadius: 24, backgroundColor: '#F66', width: 48,height: 48}}> <TouchableNativeFeedback background={TouchableNativeFeedback.Ripple('#AAF', true)}> [snip] </TouchableNativeFeedback> </View>
If this is the intented effect, it should probably be made explicit, and added to the documentation. It it's a side effect (no pun intented), #6515 still makes sense.

This causes the ripple to completely ignore the size of the button:

Button

My fix to this was to add overflow:'hidden' and the needed borderRadius to the parent view containing the TouchableNativeFeedback and then add useForeground={true} to the TouchableNativeFeedback :)

This was also the working solution for me. But what if I want the ripples in the background and not in the foreground??

Hi,

I don't know if do you have still a problem, but this is how I'm using it ( "react-native": "0.62.2",):

    buttonShape: {
        borderRadius: 50,
        overflow: 'hidden',
    },
    button: {
        backgroundColor:  'green',
        height: 80,
        width: 80,
    },

[...]

            <View style={styles.buttonShape}>
                <TouchableFeedback>
                    <View style={styles.button}>
                        <Text>
                            Floating btn
                        </Text>
                    </View>
                </TouchableFeedback>
            </View>

@Bardiamist
Copy link
Contributor

Same problem in Pressable =\

@tpakhoa1996
Copy link

tpakhoa1996 commented Aug 26, 2020

I tried every possible ways to make it work on Pressable and this problem still persist. Are there any solutions for 0.63.2 yet?

@RichardLindhout
Copy link

For me it works when I set in TouchableRipple.js the useForeground true + borderless false

  disabled={disabled}
          useForeground={true}
          background={
            background != null
              ? background
              : TouchableNativeFeedback.Ripple(calculatedRippleColor, false)
          }

Also on e.g. the FAB its needs an overflow: 'hidden' style. Works for me on both iOS and Android!

  touchable: {
    borderRadius: 28,
    overflow: 'hidden',
  },

I'm running the latest Android (9 Pie)

This still works but it's in the hidden comments now..

@mrousavy
Copy link
Contributor

The <Pressable> doesn't have a useForeground prop, how are we supposed to show ripple effects with buttons that have backgrounds (such as a LinearGradient) in a Pressable?

@nurhusni
Copy link

Is there any update and better solution to this? I've already tried each solution like wrapping the TouchableNativeFeedback in View, setForeground to true, overflow hidden, etc but none of them works. I run it on my phone on Android Pie.

@RichardLindhout
Copy link

Is there any update and better solution to this? I've already tried each solution like wrapping the TouchableNativeFeedback in View, setForeground to true, overflow hidden, etc but none of them works. I run it on my phone on Android Pie.

Have you explicitly set the borderless={false} + adding a view around it with overflow:'hidden' and the same borderRadius as the ripple?

@nurhusni
Copy link

nurhusni commented Jan 20, 2021

Have you explicitly set the borderless={false} + adding a view around it with overflow:'hidden' and the same borderRadius as the ripple?

I did. But in this case, the ripple doesn't appear at all. There's no feedback when pressing the button.

This is the code:

    <View
      style={{ backgroundColor, borderRadius: 16, overflow: "hidden" }}
    >
      <TouchableNativeFeedback
        onPress={onPress}
        useForeground={true}
        background={TouchableNativeFeedback.Ripple(rippleColor, false, 16)}
      >
        {children}
      </TouchableNativeFeedback>
    </View>

@RichardLindhout
Copy link

RichardLindhout commented Jan 20, 2021

Ok I found some code in the hidden comments from when I did have problems with this.

For me it works when I set in TouchableRipple.js the useForeground true + borderless false

  disabled={disabled}
          useForeground={true}
          background={
            background != null
              ? background
              : TouchableNativeFeedback.Ripple(calculatedRippleColor, false)
          }

Also on e.g. the FAB its needs an overflow: 'hidden' style. Works for me on both iOS and Android!

  touchable: {
    borderRadius: 28,
    overflow: 'hidden',
  },

I'm running the latest Android (9 Pie)

This did work for me

I think in your example it should be something like

<View
      style={{borderRadius: 16, overflow: "hidden" }}
    >
      <TouchableNativeFeedback
        onPress={onPress}
        style={{borderRadius: 16 }}
         useForeground={true}
         borderless={false}
        background={TouchableNativeFeedback.Ripple(rippleColor, false, 16)}
      >
        {children}
      </TouchableNativeFeedback>
    </View>
    

Maybe also consider using the Pressable component instead of the TouchableNativeFeedback

@nurhusni
Copy link

nurhusni commented Jan 21, 2021

This one works for me! Instead of wrapping the Touchable inside View, I wrap the View inside the Touchable.

    <TouchableNativeFeedback
      onPress={onPress}
      style={{borderRadius: 16 }}
      background={TouchableNativeFeedback.Ripple(rippleColor, false)}
      useForeground={true}
    >
      <View
        style={[
          { overflow: "hidden" },
        ]}
      >
        {children}
      </View>
    </TouchableNativeFeedback>

@vasilestefirta
Copy link

vasilestefirta commented Feb 16, 2021

@panjiahnh's suggestion worked for me. Here's my full Button.js component if anyone is interested. It's using TouchableOpacity on iOS and TouchableNativeFeedback on Android.

{
  "react-native": "0.63.4"
}
import React from 'react';
import PropTypes from 'prop-types';
import {
  StyleSheet,
  TouchableOpacity,
  TouchableNativeFeedback,
  Platform,
  Text,
  View,
  ViewPropTypes,
} from 'react-native';
import { colors, fonts } from '../../config/styles';

const Button = (props) => {
  const { style, type, children, disabled, onPress } = props;
  const isPrimary = type === 'primary';
  const isSecondary = type === 'secondary';
  const isTransparent = type === 'transparent';

  const buttonText = (
    <Text style={[styles.buttonText, !isPrimary && styles.buttonTextDark]}>{children}</Text>
  );

  if (Platform.OS === 'ios') {
    return (
      <TouchableOpacity
        style={[
          styles.button,
          isPrimary && styles.buttonPrimary,
          isSecondary && styles.buttonSecondary,
          style,
          disabled && styles.buttonDisabled,
        ]}
        onPress={onPress}
        disabled={disabled}
      >
        {buttonText}
      </TouchableOpacity>
    );
  }

  return (
    <TouchableNativeFeedback
      onPress={onPress}
      disabled={disabled}
      background={
        Platform.Version >= 21
          ? TouchableNativeFeedback.Ripple(
              isPrimary || isSecondary ? colors.whiteBg : colors.blueBg,
              false
            )
          : TouchableNativeFeedback.SelectableBackground(5)
      }
      useForeground={isTransparent}
    >
      <View
        style={[
          styles.button,
          isPrimary && styles.buttonPrimary,
          isSecondary && styles.buttonSecondary,
          isTransparent && { overflow: 'hidden' },
          style,
          disabled && styles.buttonDisabled,
        ]}
      >
        {buttonText}
      </View>
    </TouchableNativeFeedback>
  );
};

Button.propTypes = {
  onPress: PropTypes.func,
  style: ViewPropTypes.style,
  type: PropTypes.oneOf(['primary', 'secondary', 'transparent']),
  children: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  disabled: PropTypes.bool,
};

Button.defaultProps = {
  onPress: (f) => f,
  style: {},
  type: 'primary',
  children: '',
  disabled: false,
};

const styles = StyleSheet.create({
  button: {
    borderRadius: 27,
    height: 45,
    paddingHorizontal: 30,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonPrimary: {
    backgroundColor: colors.blueBg,
  },
  buttonSecondary: {
    backgroundColor: colors.greenBg,
  },
  buttonText: {
    color: '#FFF',
    fontSize: 16,
    fontFamily: fonts.regular,
    letterSpacing: 1.15,
    textAlign: 'center',
  },
  buttonTextDark: {
    color: colors.darkBlueText,
  },
  buttonDisabled: {
    opacity: 0.3,
  },
});

export default Button;
iOS 14 Android 10 (Pixel 4 API 29)
ios-device android-device

@SaeedZhiany
Copy link
Contributor

@vasilestefirta

using overflow: 'hidden' hides elevation effect on android in my case. I didn't test, but maybe it hides shadows on iOS too.

@safaiyeh
Copy link
Contributor

Closing as @panjiahnh suggestion works. Feel free to open a new issue if this is still happening.

@SaeedZhiany
Copy link
Contributor

SaeedZhiany commented Aug 17, 2021

Closing as @panjiahnh suggestion works. Feel free to open a new issue if this is still happening.

@safaiyeh did you read my comment? @panjiahnh suggestion is just a workaround and doesn't fix the problem correctly. please reopen this issue.

james-watkin added a commit to james-watkin/react-native-paper that referenced this issue Dec 28, 2021
Ripple effect has a lot of issues on Android P facebook/react-native#6480

Let's disable it for now and figure out how to fix/workaround it
@dsoffiantini
Copy link

dsoffiantini commented Mar 12, 2022

@panjiahnh's suggestion worked for me. Here's my full Button.js component if anyone is interested. It's using TouchableOpacity on iOS and TouchableNativeFeedback on Android.

{
  "react-native": "0.63.4"
}
import React from 'react';
import PropTypes from 'prop-types';
import {
  StyleSheet,
  TouchableOpacity,
  TouchableNativeFeedback,
  Platform,
  Text,
  View,
  ViewPropTypes,
} from 'react-native';
import { colors, fonts } from '../../config/styles';

const Button = (props) => {
  const { style, type, children, disabled, onPress } = props;
  const isPrimary = type === 'primary';
  const isSecondary = type === 'secondary';
  const isTransparent = type === 'transparent';

  const buttonText = (
    <Text style={[styles.buttonText, !isPrimary && styles.buttonTextDark]}>{children}</Text>
  );

  if (Platform.OS === 'ios') {
    return (
      <TouchableOpacity
        style={[
          styles.button,
          isPrimary && styles.buttonPrimary,
          isSecondary && styles.buttonSecondary,
          style,
          disabled && styles.buttonDisabled,
        ]}
        onPress={onPress}
        disabled={disabled}
      >
        {buttonText}
      </TouchableOpacity>
    );
  }

  return (
    <TouchableNativeFeedback
      onPress={onPress}
      disabled={disabled}
      background={
        Platform.Version >= 21
          ? TouchableNativeFeedback.Ripple(
              isPrimary || isSecondary ? colors.whiteBg : colors.blueBg,
              false
            )
          : TouchableNativeFeedback.SelectableBackground(5)
      }
      useForeground={isTransparent}
    >
      <View
        style={[
          styles.button,
          isPrimary && styles.buttonPrimary,
          isSecondary && styles.buttonSecondary,
          isTransparent && { overflow: 'hidden' },
          style,
          disabled && styles.buttonDisabled,
        ]}
      >
        {buttonText}
      </View>
    </TouchableNativeFeedback>
  );
};

Button.propTypes = {
  onPress: PropTypes.func,
  style: ViewPropTypes.style,
  type: PropTypes.oneOf(['primary', 'secondary', 'transparent']),
  children: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  disabled: PropTypes.bool,
};

Button.defaultProps = {
  onPress: (f) => f,
  style: {},
  type: 'primary',
  children: '',
  disabled: false,
};

const styles = StyleSheet.create({
  button: {
    borderRadius: 27,
    height: 45,
    paddingHorizontal: 30,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonPrimary: {
    backgroundColor: colors.blueBg,
  },
  buttonSecondary: {
    backgroundColor: colors.greenBg,
  },
  buttonText: {
    color: '#FFF',
    fontSize: 16,
    fontFamily: fonts.regular,
    letterSpacing: 1.15,
    textAlign: 'center',
  },
  buttonTextDark: {
    color: colors.darkBlueText,
  },
  buttonDisabled: {
    opacity: 0.3,
  },
});

export default Button;

This solution works for the transparent button, but it does not work for the colored buttons. It looks like it does since the effect is white, but if you try changing it to black or a color, you see the square surrounding the button still.

@HeadJk
Copy link

HeadJk commented Jun 14, 2022

I have tried these solutions, and the ripple effect is working, but the touchable is responding to presses outside of the border radius.

The red area is where it should respond to presses, and the black area is where it is actually responding to presses.
image

@gaganbiswas
Copy link

So, I tried a solution and it works as expected. The ripple effect works behind the background and it doesn't require useForeground={true}

<View style={{ borderRadius: 30, overflow: "hidden"}}>
    <TouchableNativeFeedback
          background={TouchableNativeFeedback.Ripple("#1D4ED8", false)}
          onPress={() => {}}
    >
          <View style={{ padding: 8, borderRadius: 30 }}>
              <Ionicons name="call-outline" size={24} color={"white"} />
          </View>
    </TouchableNativeFeedback>
</View>

Try this out and let me know if it works for you 👯.
P.S. Change the Ripple color to suit your needs.

crazymobdev added a commit to crazymobdev/react_native_paper that referenced this issue Aug 17, 2022
Ripple effect has a lot of issues on Android P facebook/react-native#6480

Let's disable it for now and figure out how to fix/workaround it
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests