Skip to content

Commit bd38686

Browse files
vonovakfacebook-github-bot
authored andcommitted
add ripple config object to Pressable (#28156)
Summary: Motivation is to support ripple radius just like in TouchableNativeFeedback, plus borderless attribute. See #28009 (comment) In the current form this means user needs to pass an `android_ripple` prop which is an object of this shape: ``` export type RippleConfig = {| color?: ?ColorValue, borderless?: ?boolean, radius?: ?number, |}; ``` Do we want to add methods that would create such config objects - https://facebook.github.io/react-native/docs/touchablenativefeedback#methods ? ## Changelog [Android] [Added] - support borderless and custom ripple radius on Pressable Pull Request resolved: #28156 Test Plan: Tested locally in RNTester. I noticed that when some content is rendered after the touchables, the ripple effect is "cut off" by the boundaries of the next view. This is not specific to Pressable, it happens to TouchableNativeFeedback too but I just didn't notice it before in #28009. As it is an issue of its own, I didn't investigate that. ![pressable](https://user-images.githubusercontent.com/1566403/75098762-785f2200-55ba-11ea-8842-e648317610e3.gif) I changed the Touchable example slightly too (I just moved the "custom ripple radius" up to show the "cutting off" issue), so just for completeness: ![touchable](https://user-images.githubusercontent.com/1566403/75098763-81e88a00-55ba-11ea-9528-e0343d1e054b.gif) Reviewed By: yungsters Differential Revision: D20071021 Pulled By: TheSavior fbshipit-source-id: cb553030934205a52dd50a2a8c8a20da6100e23f
1 parent 2173364 commit bd38686

File tree

7 files changed

+92
-33
lines changed

7 files changed

+92
-33
lines changed

Libraries/Components/Pressable/Pressable.js

+16-14
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
import * as React from 'react';
1414
import {useMemo, useState, useRef, useImperativeHandle} from 'react';
15-
import useAndroidRippleForView from './useAndroidRippleForView';
15+
import useAndroidRippleForView, {
16+
type RippleConfig,
17+
} from './useAndroidRippleForView';
1618
import type {
1719
AccessibilityActionEvent,
1820
AccessibilityActionInfo,
@@ -122,7 +124,7 @@ type Props = $ReadOnly<{|
122124
/**
123125
* Enables the Android ripple effect and configures its color.
124126
*/
125-
android_rippleColor?: ?ColorValue,
127+
android_ripple?: ?RippleConfig,
126128

127129
/**
128130
* Used only for documentation or testing (e.g. snapshot testing).
@@ -138,7 +140,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
138140
const {
139141
accessible,
140142
android_disableSound,
141-
android_rippleColor,
143+
android_ripple,
142144
children,
143145
delayLongPress,
144146
disabled,
@@ -156,7 +158,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
156158
const viewRef = useRef<React.ElementRef<typeof View> | null>(null);
157159
useImperativeHandle(forwardedRef, () => viewRef.current);
158160

159-
const android_ripple = useAndroidRippleForView(android_rippleColor, viewRef);
161+
const android_rippleConfig = useAndroidRippleForView(android_ripple, viewRef);
160162

161163
const [pressed, setPressed] = usePressState(testOnly_pressed === true);
162164

@@ -172,18 +174,18 @@ function Pressable(props: Props, forwardedRef): React.Node {
172174
onLongPress,
173175
onPress,
174176
onPressIn(event: PressEvent): void {
175-
if (android_ripple != null) {
176-
android_ripple.onPressIn(event);
177+
if (android_rippleConfig != null) {
178+
android_rippleConfig.onPressIn(event);
177179
}
178180
setPressed(true);
179181
if (onPressIn != null) {
180182
onPressIn(event);
181183
}
182184
},
183-
onPressMove: android_ripple?.onPressMove,
185+
onPressMove: android_rippleConfig?.onPressMove,
184186
onPressOut(event: PressEvent): void {
185-
if (android_ripple != null) {
186-
android_ripple.onPressOut(event);
187+
if (android_rippleConfig != null) {
188+
android_rippleConfig.onPressOut(event);
187189
}
188190
setPressed(false);
189191
if (onPressOut != null) {
@@ -193,7 +195,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
193195
}),
194196
[
195197
android_disableSound,
196-
android_ripple,
198+
android_rippleConfig,
197199
delayLongPress,
198200
disabled,
199201
hitSlop,
@@ -211,7 +213,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
211213
<View
212214
{...restProps}
213215
{...eventHandlers}
214-
{...android_ripple?.viewProps}
216+
{...android_rippleConfig?.viewProps}
215217
accessible={accessible !== false}
216218
focusable={focusable !== false}
217219
hitSlop={hitSlop}
@@ -227,10 +229,10 @@ function usePressState(forcePressed: boolean): [boolean, (boolean) => void] {
227229
return [pressed || forcePressed, setPressed];
228230
}
229231

230-
const MemodPressable = React.memo(React.forwardRef(Pressable));
231-
MemodPressable.displayName = 'Pressable';
232+
const MemoedPressable = React.memo(React.forwardRef(Pressable));
233+
MemoedPressable.displayName = 'Pressable';
232234

233-
export default (MemodPressable: React.AbstractComponent<
235+
export default (MemoedPressable: React.AbstractComponent<
234236
Props,
235237
React.ElementRef<typeof View>,
236238
>);

Libraries/Components/Pressable/useAndroidRippleForView.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ type NativeBackgroundProp = $ReadOnly<{|
2222
type: 'RippleAndroid',
2323
color: ?number,
2424
borderless: boolean,
25+
rippleRadius: ?number,
2526
|}>;
2627

28+
export type RippleConfig = {|
29+
color?: ?ColorValue,
30+
borderless?: ?boolean,
31+
radius?: ?number,
32+
|};
33+
2734
/**
2835
* Provides the event handlers and props for configuring the ripple effect on
2936
* supported versions of Android.
3037
*/
3138
export default function useAndroidRippleForView(
32-
rippleColor: ?ColorValue,
39+
rippleConfig: ?RippleConfig,
3340
viewRef: {|current: null | React.ElementRef<typeof View>|},
3441
): ?$ReadOnly<{|
3542
onPressIn: (event: PressEvent) => void,
@@ -39,25 +46,29 @@ export default function useAndroidRippleForView(
3946
nativeBackgroundAndroid: NativeBackgroundProp,
4047
|}>,
4148
|}> {
49+
const {color, borderless, radius} = rippleConfig ?? {};
50+
const normalizedBorderless = borderless === true;
51+
4252
return useMemo(() => {
4353
if (
4454
Platform.OS === 'android' &&
4555
Platform.Version >= 21 &&
46-
rippleColor != null
56+
(color != null || normalizedBorderless || radius != null)
4757
) {
48-
const processedColor = processColor(rippleColor);
58+
const processedColor = processColor(color);
4959
invariant(
5060
processedColor == null || typeof processedColor === 'number',
5161
'Unexpected color given for Ripple color',
5262
);
5363

5464
return {
5565
viewProps: {
56-
// Consider supporting `nativeForegroundAndroid` and `borderless`.
66+
// Consider supporting `nativeForegroundAndroid`
5767
nativeBackgroundAndroid: {
5868
type: 'RippleAndroid',
5969
color: processedColor,
60-
borderless: false,
70+
borderless: normalizedBorderless,
71+
rippleRadius: radius,
6172
},
6273
},
6374
onPressIn(event: PressEvent): void {
@@ -90,5 +101,5 @@ export default function useAndroidRippleForView(
90101
};
91102
}
92103
return null;
93-
}, [rippleColor, viewRef]);
104+
}, [color, normalizedBorderless, radius, viewRef]);
94105
}

Libraries/Components/View/ViewPropTypes.js

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ type AndroidDrawableRipple = $ReadOnly<{|
230230
type: 'RippleAndroid',
231231
color?: ?number,
232232
borderless?: ?boolean,
233+
rippleRadius?: ?number,
233234
|}>;
234235

235236
type AndroidDrawable = AndroidDrawableThemeAttr | AndroidDrawableRipple;

RNTester/js/examples/Pressable/PressableExample.js

+44-1
Original file line numberDiff line numberDiff line change
@@ -369,13 +369,56 @@ exports.examples = [
369369
};
370370
return (
371371
<View style={styles.row}>
372-
<Pressable android_rippleColor="green">
372+
<Pressable android_ripple={{color: 'green'}}>
373373
<Animated.View style={style} />
374374
</Pressable>
375375
</View>
376376
);
377377
},
378378
},
379+
{
380+
title: 'Pressable with custom Ripple',
381+
description: ("Pressable can specify ripple's radius and borderless params": string),
382+
platform: 'android',
383+
render: function(): React.Node {
384+
const nativeFeedbackButton = {
385+
textAlign: 'center',
386+
margin: 10,
387+
};
388+
return (
389+
<View
390+
style={[
391+
styles.row,
392+
{justifyContent: 'space-around', alignItems: 'center'},
393+
]}>
394+
<Pressable
395+
android_ripple={{color: 'orange', borderless: true, radius: 30}}>
396+
<View>
397+
<Text style={[styles.button, nativeFeedbackButton]}>
398+
radius 30
399+
</Text>
400+
</View>
401+
</Pressable>
402+
403+
<Pressable android_ripple={{borderless: true, radius: 150}}>
404+
<View>
405+
<Text style={[styles.button, nativeFeedbackButton]}>
406+
radius 150
407+
</Text>
408+
</View>
409+
</Pressable>
410+
411+
<Pressable android_ripple={{borderless: false, radius: 70}}>
412+
<View style={styles.block}>
413+
<Text style={[styles.button, nativeFeedbackButton]}>
414+
radius 70, with border
415+
</Text>
416+
</View>
417+
</Pressable>
418+
</View>
419+
);
420+
},
421+
},
379422
{
380423
title: '<Text onPress={fn}> with highlight',
381424
render: function(): React.Node {

RNTester/js/examples/Touchable/TouchableExample.js

+11-9
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,12 @@ function CustomRippleRadius() {
452452

453453
<TouchableNativeFeedback
454454
onPress={() => console.log('custom TNF has been clicked')}
455-
background={TouchableNativeFeedback.SelectableBackgroundBorderless(50)}>
455+
background={TouchableNativeFeedback.SelectableBackgroundBorderless(
456+
150,
457+
)}>
456458
<View>
457459
<Text style={[styles.button, styles.nativeFeedbackButton]}>
458-
radius 50
460+
radius 150
459461
</Text>
460462
</View>
461463
</TouchableNativeFeedback>
@@ -647,18 +649,18 @@ exports.examples = [
647649
},
648650
},
649651
{
650-
title: 'Disabled Touchable*',
651-
description: ('<Touchable*> components accept disabled prop which prevents ' +
652-
'any interaction with component': string),
652+
title: 'Custom Ripple Radius (Android-only)',
653+
description: ('Ripple radius on TouchableNativeFeedback can be controlled': string),
653654
render: function(): React.Element<any> {
654-
return <TouchableDisabled />;
655+
return <CustomRippleRadius />;
655656
},
656657
},
657658
{
658-
title: 'Custom Ripple Radius (Android-only)',
659-
description: ('Ripple radius on TouchableNativeFeedback can be controlled': string),
659+
title: 'Disabled Touchable*',
660+
description: ('<Touchable*> components accept disabled prop which prevents ' +
661+
'any interaction with component': string),
660662
render: function(): React.Element<any> {
661-
return <CustomRippleRadius />;
663+
return <TouchableDisabled />;
662664
},
663665
},
664666
];

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private static RippleDrawable getRippleDrawable(
6969
Context context, ReadableMap drawableDescriptionDict) {
7070
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
7171
throw new JSApplicationIllegalArgumentException(
72-
"Ripple drawable is not available on " + "android API <21");
72+
"Ripple drawable is not available on android API <21");
7373
}
7474
int color = getColor(context, drawableDescriptionDict);
7575
Drawable mask = getMask(drawableDescriptionDict);
@@ -101,7 +101,7 @@ private static int getColor(Context context, ReadableMap drawableDescriptionDict
101101
return context.getResources().getColor(sResolveOutValue.resourceId);
102102
} else {
103103
throw new JSApplicationIllegalArgumentException(
104-
"Attribute colorControlHighlight " + "couldn't be resolved into a drawable");
104+
"Attribute colorControlHighlight couldn't be resolved into a drawable");
105105
}
106106
}
107107
}

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public void setBackground(Drawable drawable) {
186186

187187
public void setTranslucentBackgroundDrawable(@Nullable Drawable background) {
188188
// it's required to call setBackground to null, as in some of the cases we may set new
189-
// background to be a layer drawable that contains a drawable that has been previously setup
189+
// background to be a layer drawable that contains a drawable that has been setup
190190
// as a background previously. This will not work correctly as the drawable callback logic is
191191
// messed up in AOSP
192192
updateBackgroundDrawable(null);

0 commit comments

Comments
 (0)