From 456cf3db14c443c483d63aa97c88b45ffd25799b Mon Sep 17 00:00:00 2001 From: Stefanos Markidis Date: Tue, 26 Oct 2021 11:19:47 -0700 Subject: [PATCH] Fix ReactSwitch for non RippleDrawable backgrounds (#32468) Summary: ReactSwitch component is crashing on Android when it is initialised with both a backgroundColor and thumbColor, `style={{ backgroundColor: "anyColor" }} thumbColor="anyColor"`, due to IllegalCastException. When setting a background color, BaseViewManagerDelegate is calling `setBackgroundColor` which replaces the background drawable with a ColorDrawale, hence [this line](https://github.com/facebook/react-native/blob/72ea0e111fccd99456abf3f974439432145585e3/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java#L68) fails. Instead, given the ripple effect needs to be preserved, one should initialise a RippleDrawable using the current background drawable and set it as the background of the switch. Given the RippleDrawable should be preserved, overriding the `setBackgroundColor` seemed the sensible thing to do. ## Changelog [Android] [Fixed] - Fix crash when a Switch is initialised with both backgroundColor and thumbColor. Pull Request resolved: https://github.com/facebook/react-native/pull/32468 Test Plan: ### Setup: Initialise an empty React Native project. Add a switch component: `` Run the project `yarn android` ### Current state (RN 65+): Red screen will show highlighting an IllegalCastException. ### With fix: - The component is expected to have a red background. - When pressed a ripple effect shows inside the backgrounds bounding box. - Business as usual otherwise. `backgroundColor` with `thumbColor`: ![backgroundColor + thumbColor](https://user-images.githubusercontent.com/4354327/138615603-141660d2-a5cd-49d7-aa5e-9c93ebc6d680.gif) Just `thumbColor`: ![Screen Recording 2021-10-25 at 00 23 57](https://user-images.githubusercontent.com/4354327/138615658-baa380dd-2cbb-4d0f-a25e-a003ef67c977.gif) Reviewed By: ShikaSD Differential Revision: D31895690 Pulled By: cortinico fbshipit-source-id: 60af16de7db61440ccfbf11d67a3d945dd90b562 --- .../react/views/switchview/ReactSwitch.java | 30 ++++++++++++++----- .../js/examples/Switch/SwitchExample.js | 23 ++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java index 4819bf2abd9..7dbc889e4e3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitch.java @@ -10,6 +10,7 @@ package com.facebook.react.views.switchview; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Build; @@ -48,6 +49,18 @@ import androidx.appcompat.widget.SwitchCompat; } } + @Override + public void setBackgroundColor(int color) { + // Ensure RippleDrawable is preserved for >=21 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setBackground( + new RippleDrawable( + createRippleDrawableColorStateList(color), new ColorDrawable(color), null)); + } else { + super.setBackgroundColor(color); + } + } + void setColor(Drawable drawable, @Nullable Integer color) { if (color == null) { drawable.clearColorFilter(); @@ -63,14 +76,10 @@ import androidx.appcompat.widget.SwitchCompat; public void setThumbColor(@Nullable Integer color) { setColor(super.getThumbDrawable(), color); - // Set the ripple color with thumb color if >= LOLLIPOP - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - RippleDrawable ripple = (RippleDrawable) super.getBackground(); - ColorStateList customColorState = - new ColorStateList( - new int[][] {new int[] {android.R.attr.state_pressed}}, new int[] {color}); - - ripple.setColor(customColorState); + // Set the ripple color if background is instance of RippleDrawable + if (color != null && super.getBackground() instanceof RippleDrawable) { + ColorStateList customColorState = createRippleDrawableColorStateList(color); + ((RippleDrawable) super.getBackground()).setColor(customColorState); } } @@ -113,4 +122,9 @@ import androidx.appcompat.widget.SwitchCompat; setTrackColor(currentTrackColor); } } + + private ColorStateList createRippleDrawableColorStateList(@Nullable Integer color) { + return new ColorStateList( + new int[][] {new int[] {android.R.attr.state_pressed}}, new int[] {color}); + } } diff --git a/packages/rn-tester/js/examples/Switch/SwitchExample.js b/packages/rn-tester/js/examples/Switch/SwitchExample.js index ebd3bc40b44..a249fc29201 100644 --- a/packages/rn-tester/js/examples/Switch/SwitchExample.js +++ b/packages/rn-tester/js/examples/Switch/SwitchExample.js @@ -244,6 +244,23 @@ class OnChangeExample extends React.Component<{...}, $FlowFixMeState> { } } +class ContainerBackgroundColorStyleExample extends React.Component< + {...}, + $FlowFixMeState, +> { + render() { + return ( + + + + ); + } +} + exports.title = 'Switch'; exports.documentationURL = 'https://reactnative.dev/docs/switch'; exports.category = 'UI'; @@ -291,6 +308,12 @@ exports.examples = [ return ; }, }, + { + title: "The container's background color can be set", + render(): React.Element { + return ; + }, + }, ]; if (Platform.OS === 'ios') {