A recent React Native project called for implementing a slider component similar to the one in the iPhone that adjusts brightness and sound. React Native’s Slider supports customization, but it still doesn’t meet demand. After a fruitless search on GitHub, I decided to do it myself. The final result is shown in the figure below.

This article documents the implementation ideas, and the source code is available on GitHub. The component is also shipped to NPM, and can be used in projects once installed via NPM I React-native Column-Slider.

The basic idea

Use two views, one for the bottom container and one for displaying the slider values. The height of the View displayed at the top is calculated based on the total height of the slider, the maximum and minimum values of the slider, and the current value of the slider :(value-min) * height/(max-min).

Monitor the move event on the slider, calculate the change of the value according to the ratio of the moving distance in the vertical direction to the total height, and then dynamically modify the slider value in the sliding process.

The implementation process

This section mainly describes how the core functions are implemented, leaving out some of the easier functions (such as displaying the current value).

UI

The UI section consists mainly of two Views:

<View style={styles.outer}>
    <View style={styles.inner}/>
</View

const styles = StyleSheet.create({
  outer: {
    backgroundColor: '#ddd',
    height: 200.width: 80.borderRadius: 20.overflow: 'hidden'},inner: {
    height: 30.position: 'absolute',
    bottom: 0.left: 0.right: 0.backgroundColor: '#fff'}});Copy the code

Add rounded corners and overflow: ‘hidden’ to the outer View, and ‘hidden’ to the inner View.

Add the shadow

When the page color and the slider background color are the same, the slider will be hard to read, so we need to add a shadow to the outside of the slider. There is a problem with setting overflow: ‘hidden’ and setting shadows to not show directly (see issue).

So you need to wrap a View around it and shadow it.

Handle events

React Native’s gesture response system handles sliding events. The gesture-response system is not complicated, but I understand it to be a query mechanism that “asks” a View if it wants to be a responder when the user starts to touch and the touch point starts to move. As for responder, subsequent gesture operations call back the corresponding function.

There are two main functions that depend on it:

  • onStartShouldSetPanResponder: Whether the user is willing to be a responder when the user starts to touch (the moment their finger touches the screen).
  • onPanResponderMove: Triggered when the user is moving his finger around the screen without stopping or moving away from the screen.

We need to add onStartShouldSetPanResponder function and return true, that is, willing to become a responder events; Then calculate the value of the slider in onPanResponderMove based on the distance moved vertically.

We can add a gesture response callback to the View using the panresponder. create method:

constructor(props) {
  super(props);
  this._panResponder = PanResponder.create({
    onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
    onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
    onPanResponderGrant: this._handlePanResponderGrant,
    onPanResponderMove: this._handlePanResponderMove,
    onPanResponderRelease: this._handlePanResponderEnd,
    onPanResponderTerminationRequest: this._handlePanResponderRequestEnd,
    onPanResponderTerminate: this._handlePanResponderEnd,
  });
}

...

<View style={styles.outer} {...this._panResponder.panHandlers}>
  <View style={styles.inner} />
</View>
Copy the code

Some calculation

The component is not blocked. You need to add a value property to the state of the component to hold the value of the current slider. Calculate the height of the internal View by using the formula (value-min) * height/(max-min).

The value of the slider does not support clicking. Each slide operation is based on the value of the current slider. In other words, during drag, the value of the slider is equal to the value at the start of drag plus the value of drag, so when drag starts, we need to record the current value:

_handlePanResponderGrant = (a)= > {
  /* * Record the current value of the slider when dragging begins. * /
  this._moveStartValue = this._getCurrentValue();
};
Copy the code

During the drag process, the drag distance in the vertical direction can be obtained by using gesturestate. dy. Note that the drag up is negative and the drag down is positive. According to the ratio of vertical drag distance to height, value interval and current value, the dragged value can be calculated:

const ratio = (-gestureState.dy) / height;
const diff = max - min;

const value = this._moveStartValue + ratio * diff
this.setState({
  value,
});
Copy the code

At this point, the basic functionality has been implemented, as shown below.

Other details

Consider the maximum and minimum case. When calculating the slider value, do not exceed the maximum and minimum values:

const value = Math.max(
        min,
        Math.min(max, this._moveStartValue + ratio * diff),
    );
Copy the code

Processing step size. When the step size is set, each drag should be an integer multiple (rounded) of the step size:

const value = Math.max(
    min,
    Math.min(
        max,
        this._moveStartValue + Math.round(ratio * diff / step) * step,
    ),
);
Copy the code

Supports setting slider values through the value attribute. Log the value in the last property in state, and then implement getDerivedStateFromProps. If the value in this property is not the same as the value in the last property, update the state:

this.state = {
  value: props.value,
  prevValue: props.value, }; . static getDerivedStateFromProps(props, state) {if(props.value ! == state.prevValue) {return ({
      value: props.value,
      prevValue: props.value,
    });
  }
  return null;
}
Copy the code