Skip to content

Commit a649f8f

Browse files
feature: Add slide limit to iOS and Android (#432)
* Add slide limit on android and ios * Adding limit to readme * Revert babel change * upper and lower limits with negative numbers * Update snapshots * PR feedback * Update mLowerLimit and mUpperLimit defaults * Update README.md Co-authored-by: Erika Smith <[email protected]>
1 parent 10c40da commit a649f8f

File tree

15 files changed

+213
-5
lines changed

15 files changed

+213
-5
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ To use this library you need to ensure you are using the correct version of Reac
8989
| `maximumValue` | Initial maximum value of the slider.<br/>Default value is 1. | number | No | |
9090
| `minimumTrackTintColor` | The color used for the track to the left of the button.<br/>Overrides the default blue gradient image on iOS. | [color](https://reactnative.dev/docs/colors) | No | |
9191
| `minimumValue` | Initial minimum value of the slider.<br/>Default value is 0. | number | No | |
92+
| `lowerLimit` | Slide lower limit. The user won't be able to slide below this limit. | number | No | Android, iOS |
93+
| `upperLimit` | Slide upper limit. The user won't be able to slide above this limit. | number | No | Android, iOS |
9294
| `onSlidingStart` | Callback that is called when the user picks up the slider.<br/>The initial value is passed as an argument to the callback handler. | function | No | |
9395
| `onSlidingComplete` | Callback that is called when the user releases the slider, regardless if the value has changed.<br/>The current value is passed as an argument to the callback handler. | function | No | |
9496
| `onValueChange` | Callback continuously called while the user is dragging the slider. | function | No | |

example/src/Examples.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ export interface Props {
99
}
1010

1111
const SliderExample = (props: SliderProps) => {
12-
const [value, setValue] = useState(0);
12+
const [value, setValue] = useState(props.value ?? 0);
1313
return (
1414
<View>
1515
<Text style={styles.text}>{value && +value.toFixed(3)}</Text>
1616
<Slider
1717
step={0.5}
1818
style={styles.slider}
1919
{...props}
20+
value={value}
2021
onValueChange={setValue}
2122
/>
2223
</View>
@@ -103,6 +104,36 @@ export const examples: Props[] = [
103104
return <SliderExample step={0.25} tapToSeek={true} />;
104105
},
105106
},
107+
{
108+
title: 'Limit on positive values [30, 80]',
109+
render() {
110+
return (
111+
<SliderExample
112+
step={1}
113+
value={40}
114+
minimumValue={0}
115+
maximumValue={120}
116+
lowerLimit={30}
117+
upperLimit={80}
118+
/>
119+
);
120+
},
121+
},
122+
{
123+
title: 'Limit on negative values [-70, -20]',
124+
render() {
125+
return (
126+
<SliderExample
127+
step={1}
128+
value={-30}
129+
minimumValue={-80}
130+
maximumValue={0}
131+
lowerLimit={-70}
132+
upperLimit={-20}
133+
/>
134+
);
135+
},
136+
},
106137
{
107138
title: 'onSlidingStart',
108139
render(): React.ReactElement {

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ public class ReactSlider extends AppCompatSeekBar {
6666

6767
private List<String> mAccessibilityIncrements;
6868

69+
/** Real limit value based on min and max values. This comes from props */
70+
private double mRealLowerLimit = Long.MIN_VALUE;
71+
72+
/** Lower limit based on the SeekBar progress 0..total steps */
73+
private int mLowerLimit;
74+
75+
/** Real limit value based on min and max values. This comes from props */
76+
private double mRealUpperLimit = Long.MAX_VALUE;
77+
78+
/** Upper limit based on the SeekBar progress 0..total steps */
79+
private int mUpperLimit;
80+
6981
public ReactSlider(Context context, @Nullable AttributeSet attrs) {
7082
super(context, attrs);
7183
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
@@ -102,6 +114,24 @@ private void disableStateListAnimatorIfNeeded() {
102114
updateAll();
103115
}
104116

117+
/* package */ void setLowerLimit(double value) {
118+
mRealLowerLimit = value;
119+
updateLowerLimit();
120+
}
121+
122+
/* package */ void setUpperLimit(double value) {
123+
mRealUpperLimit = value;
124+
updateUpperLimit();
125+
}
126+
127+
int getLowerLimit() {
128+
return this.mLowerLimit;
129+
}
130+
131+
int getUpperLimit() {
132+
return this.mUpperLimit;
133+
}
134+
105135
boolean isSliding() {
106136
return isSliding;
107137
}
@@ -186,9 +216,23 @@ private void updateAll() {
186216
mStepCalculated = (mMaxValue - mMinValue) / (double) DEFAULT_TOTAL_STEPS;
187217
}
188218
setMax(getTotalSteps());
219+
updateLowerLimit();
220+
updateUpperLimit();
189221
updateValue();
190222
}
191223

224+
/** Update limit based on props limit, max and min */
225+
private void updateLowerLimit() {
226+
double limit = Math.max(mRealLowerLimit, mMinValue);
227+
mLowerLimit = (int) Math.round((limit - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps());
228+
}
229+
230+
/** Update limit based on props limit, max and min */
231+
private void updateUpperLimit() {
232+
double limit = Math.min(mRealUpperLimit, mMaxValue);
233+
mUpperLimit = (int) Math.round((limit - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps());
234+
}
235+
192236
/** Update value only (optimization in case only value is set). */
193237
private void updateValue() {
194238
setProgress((int) Math.round((mValue - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps()));

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public static void setMaximumValue(ReactSlider view, float value) {
5252
view.setMaxValue(value);
5353
}
5454

55+
public static void setLowerLimit(ReactSlider view, double value) {
56+
view.setLowerLimit(value);
57+
}
58+
59+
public static void setUpperLimit(ReactSlider view, double value) {
60+
view.setUpperLimit(value);
61+
}
62+
5563
public static void setStep(ReactSlider view, float value) {
5664
view.setStep(value);
5765
}

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,20 @@ protected ViewManagerDelegate<ReactSlider> getDelegate() {
4040
new SeekBar.OnSeekBarChangeListener() {
4141
@Override
4242
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
43+
ReactSlider slider = (ReactSlider)seekbar;
44+
45+
if(progress < slider.getLowerLimit()) {
46+
progress = slider.getLowerLimit();
47+
seekbar.setProgress(progress);
48+
} else if (progress > slider.getUpperLimit()) {
49+
progress = slider.getUpperLimit();
50+
seekbar.setProgress(progress);
51+
}
52+
4353
ReactContext reactContext = (ReactContext) seekbar.getContext();
4454
int reactTag = seekbar.getId();
4555
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag)
46-
.dispatchEvent(new ReactSliderEvent(reactTag, ((ReactSlider)seekbar).toRealProgress(progress), fromUser));
56+
.dispatchEvent(new ReactSliderEvent(reactTag, slider.toRealProgress(progress), fromUser));
4757
}
4858

4959
@Override
@@ -154,6 +164,16 @@ public void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibi
154164
ReactSliderManagerImpl.setAccessibilityIncrements(view, accessibilityIncrements);
155165
}
156166

167+
@ReactProp(name = "lowerLimit")
168+
public void setLowerLimit(ReactSlider view, float value) {
169+
ReactSliderManagerImpl.setLowerLimit(view, value);
170+
}
171+
172+
@ReactProp(name = "upperLimit")
173+
public void setUpperLimit(ReactSlider view, float value) {
174+
ReactSliderManagerImpl.setUpperLimit(view, value);
175+
}
176+
157177
@Override
158178
@ReactProp(name = "thumbImage")
159179
public void setThumbImage(ReactSlider view, @androidx.annotation.Nullable ReadableMap source) {

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,21 @@ public class ReactSliderManager extends SimpleViewManager<ReactSlider> {
2828
new SeekBar.OnSeekBarChangeListener() {
2929
@Override
3030
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
31+
ReactSlider slider = (ReactSlider)seekbar;
32+
33+
if(progress < slider.getLowerLimit()) {
34+
progress = slider.getLowerLimit();
35+
seekbar.setProgress(progress);
36+
} else if(progress > slider.getUpperLimit()) {
37+
progress = slider.getUpperLimit();
38+
seekbar.setProgress(progress);
39+
}
40+
3141
ReactContext reactContext = (ReactContext) seekbar.getContext();
3242
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
3343
new ReactSliderEvent(
3444
seekbar.getId(),
35-
((ReactSlider)seekbar).toRealProgress(progress), fromUser));
45+
slider.toRealProgress(progress), fromUser));
3646
}
3747

3848
@Override
@@ -136,6 +146,16 @@ public void setMaximumValue(ReactSlider view, float value) {
136146
ReactSliderManagerImpl.setMaximumValue(view, value);
137147
}
138148

149+
@ReactProp(name = "lowerLimit")
150+
public void setLowerLimit(ReactSlider view, float value) {
151+
ReactSliderManagerImpl.setLowerLimit(view, value);
152+
}
153+
154+
@ReactProp(name = "upperLimit")
155+
public void setUpperLimit(ReactSlider view, float value) {
156+
ReactSliderManagerImpl.setUpperLimit(view, value);
157+
}
158+
139159
@ReactProp(name = "step", defaultFloat = 0f)
140160
public void setStep(ReactSlider view, float value) {
141161
ReactSliderManagerImpl.setStep(view, value);

package/ios/RNCSlider.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
@property (nonatomic, assign) float lastValue;
2020
@property (nonatomic, assign) bool isSliding;
2121

22+
@property (nonatomic, assign) float lowerLimit;
23+
@property (nonatomic, assign) float upperLimit;
24+
2225
@property (nonatomic, strong) UIImage *trackImage;
2326
@property (nonatomic, strong) UIImage *minimumTrackImage;
2427
@property (nonatomic, strong) UIImage *maximumTrackImage;

package/ios/RNCSliderComponentView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ typedef void (^RNCLoadImageFailureBlock)();
1919
@property (nonatomic, assign) float lastValue;
2020
@property (nonatomic, assign) bool isSliding;
2121

22+
@property (nonatomic, assign) float lowerLimit;
23+
@property (nonatomic, assign) float upperLimit;
24+
2225
@property (nonatomic, strong) UIImage *trackImage;
2326
@property (nonatomic, strong) UIImage *minimumTrackImage;
2427
@property (nonatomic, strong) UIImage *maximumTrackImage;

package/ios/RNCSliderComponentView.mm

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#import "RCTFabricComponentsPlugins.h"
1515
#import "RNCSlider.h"
1616

17-
1817
using namespace facebook::react;
1918

2019
@interface RNCSliderComponentView () <RCTRNCSliderViewProtocol>
@@ -118,6 +117,14 @@ - (void)RNCSendSliderEvent:(RNCSlider *)sender withContinuous:(BOOL)continuous i
118117
{
119118
float value = [sender discreteValue:sender.value];
120119

120+
if (value < sender.lowerLimit) {
121+
value = sender.lowerLimit;
122+
[sender setValue:value animated:NO];
123+
} else if (value > sender.upperLimit) {
124+
value = sender.upperLimit;
125+
[sender setValue:value animated:NO];
126+
}
127+
121128
if(!sender.isSliding) {
122129
[sender setValue:value animated:NO];
123130
}
@@ -163,6 +170,12 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
163170
if (oldScreenProps.maximumValue != newScreenProps.maximumValue) {
164171
[slider setMaximumValue:newScreenProps.maximumValue];
165172
}
173+
if (oldScreenProps.lowerLimit != newScreenProps.lowerLimit) {
174+
slider.lowerLimit = newScreenProps.lowerLimit;
175+
}
176+
if (oldScreenProps.upperLimit != newScreenProps.upperLimit) {
177+
slider.upperLimit = newScreenProps.upperLimit;
178+
}
166179
if (oldScreenProps.tapToSeek != newScreenProps.tapToSeek) {
167180
slider.tapToSeek = newScreenProps.tapToSeek;
168181
}

package/ios/RNCSliderManager.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ static void RNCSendSliderEvent(RNCSlider *sender, BOOL continuous, BOOL isSlidin
8787
{
8888
float value = [sender discreteValue:sender.value];
8989

90+
if (value < sender.lowerLimit) {
91+
value = sender.lowerLimit;
92+
[sender setValue:value animated:NO];
93+
} else if (value > sender.upperLimit) {
94+
value = sender.upperLimit;
95+
[sender setValue:value animated:NO];
96+
}
97+
9098
if(!sender.isSliding) {
9199
[sender setValue:value animated:NO];
92100
}
@@ -144,6 +152,8 @@ - (void)sliderTouchEnd:(RNCSlider *)sender
144152
RCT_EXPORT_VIEW_PROPERTY(maximumTrackImage, UIImage);
145153
RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
146154
RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
155+
RCT_EXPORT_VIEW_PROPERTY(lowerLimit, float);
156+
RCT_EXPORT_VIEW_PROPERTY(upperLimit, float);
147157
RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor);
148158
RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor);
149159
RCT_EXPORT_VIEW_PROPERTY(onRNCSliderValueChange, RCTBubblingEventBlock);

0 commit comments

Comments
 (0)