Skip to content

Commit 31950cf

Browse files
committed
feat: add thumbSize to set size of thumb
1 parent c203192 commit 31950cf

File tree

12 files changed

+199
-13
lines changed

12 files changed

+199
-13
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import Slider from '@react-native-community/slider';
4747
maximumValue={1}
4848
minimumTrackTintColor="#FFFFFF"
4949
maximumTrackTintColor="#000000"
50+
thumbSize={32}
5051
/>
5152
```
5253

@@ -93,6 +94,7 @@ To use this library you need to ensure you are using the correct version of Reac
9394
| `inverted` | Reverses the direction of the slider.<br/>Default value is false. | bool | |
9495
| `vertical` | Changes the orientation of the slider to vertical, if set to `true`.<br/>Default value is false. | bool | Windows |
9596
| `thumbTintColor` | Color of the foreground switch grip.<br/>**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 |
97+
| `thumbSize` | Sets the size (width and height) of the thumb.<br/>If `thumbImage` is provided, it will be scaled to this size.<br/>Units: points on iOS, dp on Android. | number | Android, iOS, Web |
9698
| `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<br/>.propTypes<br/>.source | iOS |
9799
| `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<br/>.propTypes<br/>.source | iOS |
98100
| `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<br/>.propTypes<br/>.source | |

example/src/Examples.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,4 +744,21 @@ export const examples: Props[] = [
744744
return <SliderExample disabled value={0.6} />;
745745
},
746746
},
747+
{
748+
title: 'Custom thumb size (no image)',
749+
render() {
750+
return <SliderExample thumbTintColor={'blue'} thumbSize={32} />;
751+
},
752+
},
753+
{
754+
title: 'Custom thumb size (scaled image)',
755+
render() {
756+
return (
757+
<SliderExample
758+
thumbImage={require('./resources/uie_thumb_big.png')}
759+
thumbSize={60}
760+
/>
761+
);
762+
},
763+
},
747764
];

package/android/src/main/java/com/reactnativecommunity/slider/ReactSlider.java

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import android.content.Context;
44
import android.graphics.Bitmap;
55
import android.graphics.BitmapFactory;
6+
import android.graphics.Canvas;
7+
import android.graphics.Paint;
8+
import android.graphics.PorterDuff;
69
import android.graphics.drawable.BitmapDrawable;
710
import android.os.Build;
811
import android.util.Log;
@@ -74,6 +77,15 @@ public class ReactSlider extends AppCompatSeekBar {
7477
/** Upper limit based on the SeekBar progress 0..total steps */
7578
private int mUpperLimit;
7679

80+
/** Thumb size in pixels (0 = default) */
81+
private int mThumbSizePx = 0;
82+
83+
/** Original thumb drawable URI */
84+
@Nullable private String mThumbImageUri = null;
85+
86+
/** Cached thumb tint color */
87+
@Nullable private Integer mThumbTintColor = null;
88+
7789
public ReactSlider(Context context, @Nullable AttributeSet attrs) {
7890
super(context, attrs);
7991
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
@@ -299,15 +311,88 @@ public BitmapDrawable call() {
299311
return bitmapDrawable;
300312
}
301313

302-
public void setThumbImage(final String uri) {
303-
if (uri != null) {
304-
setThumb(getBitmapDrawable(uri));
305-
// Enable alpha channel for the thumbImage
314+
public void setThumbImage(@Nullable final String uri) {
315+
mThumbImageUri = uri;
316+
updateThumbImage();
317+
}
318+
319+
public void setThumbSize(final double size) {
320+
float density = getResources().getDisplayMetrics().density;
321+
mThumbSizePx = size > 0 ? Math.round((float) size * density) : 0;
322+
updateThumbImage();
323+
}
324+
325+
public void setThumbTintColor(@Nullable final Integer color) {
326+
mThumbTintColor = color;
327+
if (mThumbImageUri != null || mThumbSizePx > 0) {
328+
updateThumbImage();
329+
} else {
330+
applyThumbTintColorFilter();
331+
}
332+
}
333+
334+
private void applyThumbTintColorFilter() {
335+
if (getThumb() == null) {
336+
return;
337+
}
338+
339+
if (mThumbTintColor != null) {
340+
getThumb().setColorFilter(mThumbTintColor, PorterDuff.Mode.SRC_IN);
341+
} else {
342+
getThumb().clearColorFilter();
343+
}
344+
}
345+
346+
private void updateThumbImage() {
347+
if (mThumbImageUri != null) {
348+
BitmapDrawable drawable = getBitmapDrawable(mThumbImageUri);
349+
if (drawable != null) {
350+
if (mThumbSizePx > 0) {
351+
Bitmap originalBitmap = drawable.getBitmap();
352+
Bitmap scaledBitmap =
353+
Bitmap.createScaledBitmap(originalBitmap, mThumbSizePx, mThumbSizePx, true);
354+
setThumb(new BitmapDrawable(getResources(), scaledBitmap));
355+
} else {
356+
setThumb(drawable);
357+
}
358+
applyThumbTintColorFilter();
359+
// Enable alpha channel for the thumbImage
360+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
361+
setSplitTrack(false);
362+
}
363+
return;
364+
}
365+
}
366+
367+
if (mThumbSizePx > 0) {
368+
Bitmap bitmap = Bitmap.createBitmap(mThumbSizePx, mThumbSizePx, Bitmap.Config.ARGB_8888);
369+
Canvas canvas = new Canvas(bitmap);
370+
371+
int fillColor =
372+
mThumbTintColor != null
373+
? mThumbTintColor
374+
: (getThumbTintList() != null ? getThumbTintList().getDefaultColor() : 0xFFFFFFFF);
375+
376+
Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
377+
fillPaint.setStyle(Paint.Style.FILL);
378+
fillPaint.setColor(fillColor);
379+
float radius = mThumbSizePx / 2f;
380+
canvas.drawCircle(radius, radius, radius, fillPaint);
381+
382+
Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
383+
strokePaint.setStyle(Paint.Style.STROKE);
384+
strokePaint.setStrokeWidth(1);
385+
strokePaint.setColor(0x1A000000);
386+
canvas.drawCircle(radius, radius, radius - 0.5f, strokePaint);
387+
388+
setThumb(new BitmapDrawable(getResources(), bitmap));
389+
applyThumbTintColorFilter();
306390
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
307391
setSplitTrack(false);
308392
}
309393
} else {
310-
setThumb(getThumb());
394+
// No special sizing; keep existing thumb, only apply tint if needed.
395+
applyThumbTintColorFilter();
311396
}
312397
}
313398
}

package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ public void setThumbTintColor(ReactSlider view, Integer color) {
132132
ReactSliderManagerImpl.setThumbTintColor(view, color);
133133
}
134134

135+
@Override
136+
@ReactProp(name = "thumbSize", defaultFloat = 0f)
137+
public void setThumbSize(ReactSlider view, double size) {
138+
ReactSliderManagerImpl.setThumbSize(view, size);
139+
}
140+
135141
@Override
136142
@ReactProp(name = "minimumTrackTintColor", customType = "Color")
137143
public void setMinimumTrackTintColor(ReactSlider view, Integer color) {

package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,7 @@ public static void setDisabled(ReactSlider view, boolean disabled) {
6969
}
7070

7171
public static void setThumbTintColor(ReactSlider view, Integer color) {
72-
if (view.getThumb() != null) {
73-
if (color == null) {
74-
view.getThumb().clearColorFilter();
75-
} else {
76-
view.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
77-
}
78-
}
72+
view.setThumbTintColor(color);
7973
}
8074

8175
public static void setMinimumTrackTintColor(ReactSlider view, Integer color) {
@@ -101,6 +95,10 @@ public static void setThumbImage(ReactSlider view, @Nullable ReadableMap source)
10195
view.setThumbImage(uri);
10296
}
10397

98+
public static void setThumbSize(ReactSlider view, double size) {
99+
view.setThumbSize(size);
100+
}
101+
104102
public static void setMaximumTrackTintColor(ReactSlider view, Integer color) {
105103
LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent();
106104
Drawable background = drawable.findDrawableByLayerId(android.R.id.background);

package/ios/RNCSlider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
@property (nonatomic, strong) UIImage *minimumTrackImage;
2020
@property (nonatomic, strong) UIImage *maximumTrackImage;
2121
@property (nonatomic, strong) UIImage *thumbImage;
22+
@property (nonatomic, assign) CGFloat thumbSize;
2223
@property (nonatomic, assign) bool tapToSeek;
2324
@property (nonatomic, strong) NSString *accessibilityUnits;
2425
@property (nonatomic, strong) NSArray *accessibilityIncrements;
2526

2627
- (float) discreteValue:(float)value;
2728
- (void) setDisabled:(bool)disabled;
29+
- (void) updateThumbImage;
2830

2931
@end

package/ios/RNCSlider.m

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ @implementation RNCSlider
55
float _unclippedValue;
66
bool _minimumTrackImageSet;
77
bool _maximumTrackImageSet;
8+
UIImage *_thumbImage;
9+
CGFloat _thumbSize;
810
}
911

1012
- (instancetype)init {
@@ -116,14 +118,57 @@ - (UIImage *)maximumTrackImage
116118

117119
- (void)setThumbImage:(UIImage *)thumbImage
118120
{
119-
[self setThumbImage:thumbImage forState:UIControlStateNormal];
121+
_thumbImage = thumbImage;
122+
[self updateThumbImage];
120123
}
121124

122125
- (UIImage *)thumbImage
123126
{
124127
return [self thumbImageForState:UIControlStateNormal];
125128
}
126129

130+
- (void)setThumbSize:(CGFloat)thumbSize
131+
{
132+
_thumbSize = thumbSize;
133+
[self updateThumbImage];
134+
}
135+
136+
- (void)updateThumbImage
137+
{
138+
UIImage *imageToSet = nil;
139+
140+
if (_thumbSize > 0) {
141+
CGSize newSize = CGSizeMake(_thumbSize, _thumbSize);
142+
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
143+
CGContextRef context = UIGraphicsGetCurrentContext();
144+
145+
if (_thumbImage) {
146+
[_thumbImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
147+
} else {
148+
UIColor *fillColor = self.thumbTintColor ?: [UIColor whiteColor];
149+
CGContextSetFillColorWithColor(context, fillColor.CGColor);
150+
CGContextFillEllipseInRect(context, CGRectMake(0, 0, newSize.width, newSize.height));
151+
152+
CGContextSetStrokeColorWithColor(context, [[UIColor colorWithWhite:0.0 alpha:0.1] CGColor]);
153+
CGContextSetLineWidth(context, 0.5);
154+
CGContextStrokeEllipseInRect(
155+
context,
156+
CGRectMake(0.25, 0.25, newSize.width - 0.5, newSize.height - 0.5));
157+
}
158+
159+
imageToSet = UIGraphicsGetImageFromCurrentImageContext();
160+
UIGraphicsEndImageContext();
161+
} else if (_thumbImage) {
162+
imageToSet = _thumbImage;
163+
}
164+
165+
if (imageToSet) {
166+
[self setThumbImage:imageToSet forState:UIControlStateNormal];
167+
[self setThumbImage:imageToSet forState:UIControlStateHighlighted];
168+
[self setThumbImage:imageToSet forState:UIControlStateSelected];
169+
}
170+
}
171+
127172
- (void)setInverted:(BOOL)inverted
128173
{
129174
if (inverted) {

package/ios/RNCSliderComponentView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ typedef void (^RNCLoadImageFailureBlock)();
2424
@property (nonatomic, strong) UIImage *minimumTrackImage;
2525
@property (nonatomic, strong) UIImage *maximumTrackImage;
2626
@property (nonatomic, strong) UIImage *thumbImage;
27+
@property (nonatomic, assign) CGFloat thumbSize;
2728
@property (nonatomic, assign) bool tapToSeek;
2829
@property (nonatomic, strong) NSString *accessibilityUnits;
2930
@property (nonatomic, strong) NSArray *accessibilityIncrements;

package/ios/RNCSliderComponentView.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
208208
if (oldScreenProps.thumbTintColor != newScreenProps.thumbTintColor) {
209209
slider.thumbTintColor = RCTUIColorFromSharedColor(newScreenProps.thumbTintColor);
210210
}
211+
if (oldScreenProps.thumbSize != newScreenProps.thumbSize) {
212+
slider.thumbSize = newScreenProps.thumbSize;
213+
}
211214
if (oldScreenProps.minimumTrackTintColor != newScreenProps.minimumTrackTintColor) {
212215
slider.minimumTrackTintColor = RCTUIColorFromSharedColor(newScreenProps.minimumTrackTintColor);
213216
}
@@ -236,6 +239,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
236239
[self->slider setThumbImage:nil];
237240
}];
238241
}
242+
if (oldScreenProps.thumbTintColor != newScreenProps.thumbTintColor && slider.thumbSize > 0) {
243+
[slider updateThumbImage];
244+
}
239245
if (oldScreenProps.trackImage != newScreenProps.trackImage) {
240246
[self loadImageFromImageSource:newScreenProps.trackImage completionBlock:^(NSError *error, UIImage *image) {
241247
dispatch_async(dispatch_get_main_queue(), ^{

package/src/RNCSliderNativeComponent.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ export interface NativeProps extends ViewProps {
3434
testID?: string;
3535
thumbImage?: ImageSource;
3636
thumbTintColor?: ColorValue;
37+
/**
38+
* Sets the size (width and height) of the thumb, in points (dp on Android).
39+
* If you also set `thumbImage`, the image will be scaled to this size.
40+
*/
41+
thumbSize?: Double;
3742
trackImage?: ImageSource;
3843
value?: Float;
3944
lowerLimit?: Float;

0 commit comments

Comments
 (0)