useAndroidRippleForView.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @flow strict-local
  8. * @format
  9. */
  10. 'use strict';
  11. import invariant from 'invariant';
  12. import {Commands} from '../View/ViewNativeComponent';
  13. import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
  14. import type {PressEvent} from '../../Types/CoreEventTypes';
  15. import {Platform, View, processColor} from 'react-native';
  16. import * as React from 'react';
  17. import {useMemo} from 'react';
  18. type NativeBackgroundProp = $ReadOnly<{|
  19. type: 'RippleAndroid',
  20. color: ?number,
  21. borderless: boolean,
  22. rippleRadius: ?number,
  23. |}>;
  24. export type RippleConfig = {|
  25. color?: ?ColorValue,
  26. borderless?: ?boolean,
  27. radius?: ?number,
  28. |};
  29. /**
  30. * Provides the event handlers and props for configuring the ripple effect on
  31. * supported versions of Android.
  32. */
  33. export default function useAndroidRippleForView(
  34. rippleConfig: ?RippleConfig,
  35. viewRef: {|current: null | React.ElementRef<typeof View>|},
  36. ): ?$ReadOnly<{|
  37. onPressIn: (event: PressEvent) => void,
  38. onPressMove: (event: PressEvent) => void,
  39. onPressOut: (event: PressEvent) => void,
  40. viewProps: $ReadOnly<{|
  41. nativeBackgroundAndroid: NativeBackgroundProp,
  42. |}>,
  43. |}> {
  44. const {color, borderless, radius} = rippleConfig ?? {};
  45. const normalizedBorderless = borderless === true;
  46. return useMemo(() => {
  47. if (
  48. Platform.OS === 'android' &&
  49. Platform.Version >= 21 &&
  50. (color != null || normalizedBorderless || radius != null)
  51. ) {
  52. const processedColor = processColor(color);
  53. invariant(
  54. processedColor == null || typeof processedColor === 'number',
  55. 'Unexpected color given for Ripple color',
  56. );
  57. return {
  58. viewProps: {
  59. // Consider supporting `nativeForegroundAndroid`
  60. nativeBackgroundAndroid: {
  61. type: 'RippleAndroid',
  62. color: processedColor,
  63. borderless: normalizedBorderless,
  64. rippleRadius: radius,
  65. },
  66. },
  67. onPressIn(event: PressEvent): void {
  68. const view = viewRef.current;
  69. if (view != null) {
  70. Commands.setPressed(view, true);
  71. Commands.hotspotUpdate(
  72. view,
  73. event.nativeEvent.locationX ?? 0,
  74. event.nativeEvent.locationY ?? 0,
  75. );
  76. }
  77. },
  78. onPressMove(event: PressEvent): void {
  79. const view = viewRef.current;
  80. if (view != null) {
  81. Commands.hotspotUpdate(
  82. view,
  83. event.nativeEvent.locationX ?? 0,
  84. event.nativeEvent.locationY ?? 0,
  85. );
  86. }
  87. },
  88. onPressOut(event: PressEvent): void {
  89. const view = viewRef.current;
  90. if (view != null) {
  91. Commands.setPressed(view, false);
  92. }
  93. },
  94. };
  95. }
  96. return null;
  97. }, [color, normalizedBorderless, radius, viewRef]);
  98. }