diff --git a/README.md b/README.md
index 56902cb8..4d42aa5d 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ import Slider from '@react-native-community/slider';
maximumValue={1}
minimumTrackTintColor="#FFFFFF"
maximumTrackTintColor="#000000"
+ thumbSize={32}
/>
```
@@ -93,6 +94,7 @@ To use this library you need to ensure you are using the correct version of Reac
| `inverted` | Reverses the direction of the slider.
Default value is false. | bool | |
| `vertical` | Changes the orientation of the slider to vertical, if set to `true`.
Default value is false. | bool | Windows |
| `thumbTintColor` | Color of the foreground switch grip.
**NOTE:** This prop will override the `thumbImage` prop set, meaning that if both `thumbImage` and `thumbTintColor` will be set, image used for the thumb may not be displayed correctly! | [color](https://reactnative.dev/docs/colors) | Android |
+| `thumbSize` | Sets the size (width and height) of the thumb.
If `thumbImage` is provided, it will be scaled to this size.
Units: points on iOS, dp on Android. | number | Android, iOS, Web |
| `maximumTrackImage` | Assigns a maximum track image. Only static images are supported. The leftmost pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | iOS |
| `minimumTrackImage` | Assigns a minimum track image. Only static images are supported. The rightmost pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | iOS |
| `thumbImage` | Sets an image for the thumb. Only static images are supported. Needs to be a URI of a local or network image; base64-encoded SVG is not supported. | Image
.propTypes
.source | |
diff --git a/example/src/Examples.tsx b/example/src/Examples.tsx
index 4724d4ee..cb218c63 100644
--- a/example/src/Examples.tsx
+++ b/example/src/Examples.tsx
@@ -744,4 +744,21 @@ export const examples: Props[] = [
return ;
},
},
+ {
+ title: 'Custom thumb size (no image)',
+ render() {
+ return ;
+ },
+ },
+ {
+ title: 'Custom thumb size (scaled image)',
+ render() {
+ return (
+
+ );
+ },
+ },
];
diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java
index 0ff53306..30487bed 100644
--- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java
+++ b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java
@@ -3,6 +3,9 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.Log;
@@ -74,6 +77,15 @@ public class ReactSlider extends AppCompatSeekBar {
/** Upper limit based on the SeekBar progress 0..total steps */
private int mUpperLimit;
+ /** Thumb size in pixels (0 = default) */
+ private int mThumbSizePx = 0;
+
+ /** Original thumb drawable URI */
+ @Nullable private String mThumbImageUri = null;
+
+ /** Cached thumb tint color */
+ @Nullable private Integer mThumbTintColor = null;
+
public ReactSlider(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
@@ -299,15 +311,88 @@ public BitmapDrawable call() {
return bitmapDrawable;
}
- public void setThumbImage(final String uri) {
- if (uri != null) {
- setThumb(getBitmapDrawable(uri));
- // Enable alpha channel for the thumbImage
+ public void setThumbImage(@Nullable final String uri) {
+ mThumbImageUri = uri;
+ updateThumbImage();
+ }
+
+ public void setThumbSize(final double size) {
+ float density = getResources().getDisplayMetrics().density;
+ mThumbSizePx = size > 0 ? Math.round((float) size * density) : 0;
+ updateThumbImage();
+ }
+
+ public void setThumbTintColor(@Nullable final Integer color) {
+ mThumbTintColor = color;
+ if (mThumbImageUri != null || mThumbSizePx > 0) {
+ updateThumbImage();
+ } else {
+ applyThumbTintColorFilter();
+ }
+ }
+
+ private void applyThumbTintColorFilter() {
+ if (getThumb() == null) {
+ return;
+ }
+
+ if (mThumbTintColor != null) {
+ getThumb().setColorFilter(mThumbTintColor, PorterDuff.Mode.SRC_IN);
+ } else {
+ getThumb().clearColorFilter();
+ }
+ }
+
+ private void updateThumbImage() {
+ if (mThumbImageUri != null) {
+ BitmapDrawable drawable = getBitmapDrawable(mThumbImageUri);
+ if (drawable != null) {
+ if (mThumbSizePx > 0) {
+ Bitmap originalBitmap = drawable.getBitmap();
+ Bitmap scaledBitmap =
+ Bitmap.createScaledBitmap(originalBitmap, mThumbSizePx, mThumbSizePx, true);
+ setThumb(new BitmapDrawable(getResources(), scaledBitmap));
+ } else {
+ setThumb(drawable);
+ }
+ applyThumbTintColorFilter();
+ // Enable alpha channel for the thumbImage
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ setSplitTrack(false);
+ }
+ return;
+ }
+ }
+
+ if (mThumbSizePx > 0) {
+ Bitmap bitmap = Bitmap.createBitmap(mThumbSizePx, mThumbSizePx, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ int fillColor =
+ mThumbTintColor != null
+ ? mThumbTintColor
+ : (getThumbTintList() != null ? getThumbTintList().getDefaultColor() : 0xFFFFFFFF);
+
+ Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ fillPaint.setStyle(Paint.Style.FILL);
+ fillPaint.setColor(fillColor);
+ float radius = mThumbSizePx / 2f;
+ canvas.drawCircle(radius, radius, radius, fillPaint);
+
+ Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ strokePaint.setStyle(Paint.Style.STROKE);
+ strokePaint.setStrokeWidth(1);
+ strokePaint.setColor(0x1A000000);
+ canvas.drawCircle(radius, radius, radius - 0.5f, strokePaint);
+
+ setThumb(new BitmapDrawable(getResources(), bitmap));
+ applyThumbTintColorFilter();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setSplitTrack(false);
}
} else {
- setThumb(getThumb());
+ // No special sizing; keep existing thumb, only apply tint if needed.
+ applyThumbTintColorFilter();
}
}
}
diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java
index feb5753b..d2a0f547 100644
--- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java
+++ b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java
@@ -132,6 +132,12 @@ public void setThumbTintColor(ReactSlider view, Integer color) {
ReactSliderManagerImpl.setThumbTintColor(view, color);
}
+ @Override
+ @ReactProp(name = "thumbSize", defaultFloat = 0f)
+ public void setThumbSize(ReactSlider view, double size) {
+ ReactSliderManagerImpl.setThumbSize(view, size);
+ }
+
@Override
@ReactProp(name = "minimumTrackTintColor", customType = "Color")
public void setMinimumTrackTintColor(ReactSlider view, Integer color) {
diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java
index dff1f9dd..f97188a5 100644
--- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java
+++ b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java
@@ -69,13 +69,7 @@ public static void setDisabled(ReactSlider view, boolean disabled) {
}
public static void setThumbTintColor(ReactSlider view, Integer color) {
- if (view.getThumb() != null) {
- if (color == null) {
- view.getThumb().clearColorFilter();
- } else {
- view.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
- }
- }
+ view.setThumbTintColor(color);
}
public static void setMinimumTrackTintColor(ReactSlider view, Integer color) {
@@ -101,6 +95,10 @@ public static void setThumbImage(ReactSlider view, @Nullable ReadableMap source)
view.setThumbImage(uri);
}
+ public static void setThumbSize(ReactSlider view, double size) {
+ view.setThumbSize(size);
+ }
+
public static void setMaximumTrackTintColor(ReactSlider view, Integer color) {
LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent();
Drawable background = drawable.findDrawableByLayerId(android.R.id.background);
diff --git a/package/ios/RNCSlider.h b/package/ios/RNCSlider.h
index 570ec53c..c6cb3c70 100644
--- a/package/ios/RNCSlider.h
+++ b/package/ios/RNCSlider.h
@@ -19,11 +19,13 @@
@property (nonatomic, strong) UIImage *minimumTrackImage;
@property (nonatomic, strong) UIImage *maximumTrackImage;
@property (nonatomic, strong) UIImage *thumbImage;
+@property (nonatomic, assign) CGFloat thumbSize;
@property (nonatomic, assign) bool tapToSeek;
@property (nonatomic, strong) NSString *accessibilityUnits;
@property (nonatomic, strong) NSArray *accessibilityIncrements;
- (float) discreteValue:(float)value;
- (void) setDisabled:(bool)disabled;
+- (void) updateThumbImage;
@end
diff --git a/package/ios/RNCSlider.m b/package/ios/RNCSlider.m
index 3f1cadde..9e2e2bc5 100644
--- a/package/ios/RNCSlider.m
+++ b/package/ios/RNCSlider.m
@@ -5,6 +5,8 @@ @implementation RNCSlider
float _unclippedValue;
bool _minimumTrackImageSet;
bool _maximumTrackImageSet;
+ UIImage *_thumbImage;
+ CGFloat _thumbSize;
}
- (instancetype)init {
@@ -116,7 +118,8 @@ - (UIImage *)maximumTrackImage
- (void)setThumbImage:(UIImage *)thumbImage
{
- [self setThumbImage:thumbImage forState:UIControlStateNormal];
+ _thumbImage = thumbImage;
+ [self updateThumbImage];
}
- (UIImage *)thumbImage
@@ -124,6 +127,48 @@ - (UIImage *)thumbImage
return [self thumbImageForState:UIControlStateNormal];
}
+- (void)setThumbSize:(CGFloat)thumbSize
+{
+ _thumbSize = thumbSize;
+ [self updateThumbImage];
+}
+
+- (void)updateThumbImage
+{
+ UIImage *imageToSet = nil;
+
+ if (_thumbSize > 0) {
+ CGSize newSize = CGSizeMake(_thumbSize, _thumbSize);
+ UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ if (_thumbImage) {
+ [_thumbImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
+ } else {
+ UIColor *fillColor = self.thumbTintColor ?: [UIColor whiteColor];
+ CGContextSetFillColorWithColor(context, fillColor.CGColor);
+ CGContextFillEllipseInRect(context, CGRectMake(0, 0, newSize.width, newSize.height));
+
+ CGContextSetStrokeColorWithColor(context, [[UIColor colorWithWhite:0.0 alpha:0.1] CGColor]);
+ CGContextSetLineWidth(context, 0.5);
+ CGContextStrokeEllipseInRect(
+ context,
+ CGRectMake(0.25, 0.25, newSize.width - 0.5, newSize.height - 0.5));
+ }
+
+ imageToSet = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ } else if (_thumbImage) {
+ imageToSet = _thumbImage;
+ }
+
+ if (imageToSet) {
+ [self setThumbImage:imageToSet forState:UIControlStateNormal];
+ [self setThumbImage:imageToSet forState:UIControlStateHighlighted];
+ [self setThumbImage:imageToSet forState:UIControlStateSelected];
+ }
+}
+
- (void)setInverted:(BOOL)inverted
{
if (inverted) {
diff --git a/package/ios/RNCSliderComponentView.h b/package/ios/RNCSliderComponentView.h
index ce915326..3bb041d7 100644
--- a/package/ios/RNCSliderComponentView.h
+++ b/package/ios/RNCSliderComponentView.h
@@ -24,6 +24,7 @@ typedef void (^RNCLoadImageFailureBlock)();
@property (nonatomic, strong) UIImage *minimumTrackImage;
@property (nonatomic, strong) UIImage *maximumTrackImage;
@property (nonatomic, strong) UIImage *thumbImage;
+@property (nonatomic, assign) CGFloat thumbSize;
@property (nonatomic, assign) bool tapToSeek;
@property (nonatomic, strong) NSString *accessibilityUnits;
@property (nonatomic, strong) NSArray *accessibilityIncrements;
diff --git a/package/ios/RNCSliderComponentView.mm b/package/ios/RNCSliderComponentView.mm
index 81487b10..cd400190 100644
--- a/package/ios/RNCSliderComponentView.mm
+++ b/package/ios/RNCSliderComponentView.mm
@@ -208,6 +208,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
if (oldScreenProps.thumbTintColor != newScreenProps.thumbTintColor) {
slider.thumbTintColor = RCTUIColorFromSharedColor(newScreenProps.thumbTintColor);
}
+ if (oldScreenProps.thumbSize != newScreenProps.thumbSize) {
+ slider.thumbSize = newScreenProps.thumbSize;
+ }
if (oldScreenProps.minimumTrackTintColor != newScreenProps.minimumTrackTintColor) {
slider.minimumTrackTintColor = RCTUIColorFromSharedColor(newScreenProps.minimumTrackTintColor);
}
@@ -236,6 +239,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
[self->slider setThumbImage:nil];
}];
}
+ if (oldScreenProps.thumbTintColor != newScreenProps.thumbTintColor && slider.thumbSize > 0) {
+ [slider updateThumbImage];
+ }
if (oldScreenProps.trackImage != newScreenProps.trackImage) {
[self loadImageFromImageSource:newScreenProps.trackImage completionBlock:^(NSError *error, UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
diff --git a/package/src/RNCSliderNativeComponent.ts b/package/src/RNCSliderNativeComponent.ts
index 392dddae..e1007018 100644
--- a/package/src/RNCSliderNativeComponent.ts
+++ b/package/src/RNCSliderNativeComponent.ts
@@ -34,6 +34,11 @@ export interface NativeProps extends ViewProps {
testID?: string;
thumbImage?: ImageSource;
thumbTintColor?: ColorValue;
+ /**
+ * Sets the size (width and height) of the thumb, in points (dp on Android).
+ * If you also set `thumbImage`, the image will be scaled to this size.
+ */
+ thumbSize?: Double;
trackImage?: ImageSource;
value?: Float;
lowerLimit?: Float;
diff --git a/package/src/Slider.tsx b/package/src/Slider.tsx
index 5ac1977d..543d4dd5 100644
--- a/package/src/Slider.tsx
+++ b/package/src/Slider.tsx
@@ -166,6 +166,12 @@ type Props = ViewProps &
*/
thumbImage?: ImageSource;
+ /**
+ * Sets the size (width and height) of the thumb.
+ * If `thumbImage` is provided, it will be scaled to this size.
+ */
+ thumbSize?: number;
+
/**
* If true the slider will be inverted.
* Default value is false.
@@ -340,6 +346,7 @@ const SliderComponent = (
? 'transparent'
: props.thumbTintColor
}
+ thumbSize={props.thumbSize}
/>
);
diff --git a/package/typings/index.d.ts b/package/typings/index.d.ts
index 0b548440..762f6820 100644
--- a/package/typings/index.d.ts
+++ b/package/typings/index.d.ts
@@ -60,6 +60,12 @@ export interface SliderPropsIOS extends ReactNative.ViewProps {
*/
thumbImage?: ReactNative.ImageURISource;
+ /**
+ * Sets the size (width and height) of the thumb.
+ * If `thumbImage` is provided, it will be scaled to this size.
+ */
+ thumbSize?: number;
+
/**
* Assigns a single image for the track. Only static images
* are supported. The center pixel of the image will be stretched
@@ -166,6 +172,12 @@ export interface SliderProps
*/
inverted?: boolean;
+ /**
+ * Sets the size (width and height) of the thumb.
+ * If `thumbImage` is provided, it will be scaled to this size.
+ */
+ thumbSize?: number;
+
/**
* Component to be rendered for each step indicator.
*/