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:
`<Switch
        style={{backgroundColor: 'red'}}
        thumbColor={'https://github.com/facebook/react-native/issues/356'}
      />`
Run the project `yarn android`

### Current state (RN 65+):
Red screen will show highlighting an IllegalCastException.

<img src="https://user-images.githubusercontent.com/4354327/138616661-3ba1370c-6a2b-48c2-ba70-b99415a4256f.png" width="200"/>

### 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
This commit is contained in:
Stefanos Markidis
2021-10-26 11:21:05 -07:00
committed by Facebook GitHub Bot
parent f58c496e07
commit 456cf3db14
2 changed files with 45 additions and 8 deletions
@@ -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});
}
}