Đây là cái chúng ta sẽ tạo
Phân tích một chút, chúng ta có thể chọn 1 trong 2 cách
- Xử lý gesture bằng React Native Gesture Responder System
- Xử lý gesture mằng một thư viện native khác, cho phép chúng ta can thiệp nhiều dạng gesture phức tạp hơn như xoay, chụm ngón tay (pinch), nhấn và dữ lâu.
Với các marker như hình trên, chúng ta chỉ cần dùng React-Native Gesture Responder là đủ.
Nếu muốn dùng thư viện, bạn có thể tìm hiểu React Native Gesture Handler
Dựng static UI
import React from 'react';
import styled from 'styled-components';
type StateType = {
barHeight: number | null,
deltaValue: number,
value: number
};
const initialValue = 0;
const CIRCLE_DIAMETER = 50;
export default class Slider extends React.Component<{}, StateType> {
render() {
return (
<PageContainer>
<Value>
{Math.floor(initialValue)}
</Value>
<Container>
<BarContainer>
<Bar onLayout={this.onBarLayout} />
<Circile bottomOffset={0} />
</BarContainer>
</Container>
</PageContainer>
)
}
}
const PageContainer = styled.View`
background-color: black;
flex-grow: 1;
align-self: stretch;
align-items: center;
padding-vertical: 20;
`;
const Container = styled.View`
flex-grow: 1;
align-self: stretch;
justify-content: center;
flex-direction: row;
`;
const Value = styled.Text`
color: white;
`;
const BarContainer = styled.View`
width: ${CIRCLE_DIAMETER};
align-items: center;
padding-vertical: ${CIRCLE_DIAMETER / 2};
margin-horizontal: 20;
`;
const Bar = styled.View`
width: 2;
background-color: white;
flex-grow: 1;
`;
const Circle = styled.View`
border-radius: ${CIRCLE_DIAMETER / 2};
width: ${CIRCLE_DIAMETER};
height: ${CIRCLE_DIAMETER};
background-color: white;
position: absolute;
bottom: ${props => props.bottomOffset};
`;
Kết quả
Chúng ta muốn lấy được giá trị và đặt cái nút tròn đúng vị trí theo giá trị này. Hàm getBottomOffsetFromValue
sẽ đảm nhiệm chuyển đổi giá trị offset bottom sang giá trị tương ứng.
//...
export default class Slider extends React.Component<{}, StateType> {
state = {
barHeight: null
};
onBarLayout = (event: LayoutChangeEvent) => {
const {height: barHeight} = event.nativeEvent.layout;
this.setState({ barHeight });
};
getBottomOffsetValue = (
value: number,
rangeMin: number,
rangeMax: number,
barHeight: number | null
) => {
if (barHeight === null) return 0;
const valueOffset = value - rangeMin;
const totalRange = rangeMax - rangeMin;
const percentage = valueOffset / totalRange;
return barHeight * percentage;
}
render() {
const {barHeight} = this.state;
const bottomOffset = this.getBottomOffsetFromValue(initialValue, min, max, barHeight);
return (
// ...
<Bar onLayout={this.onBarLayout} />
<Circle bottomOffset={bottomOffset} />
// ...
)
}
}
//...
Để cái marker có thể di chuyển được, chúng ta dùng PanResponder
import {
LayoutChangeEvent,
PanResponder,
PanResponderGestureState
} from 'react-native';
// ....
export default class Slider extends React.Component({}, StateType) {
state = {
barHeight: null,
defaultValue: 0,
value: initialValue
}
// ....
panResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderMove: (_, gestureState) => this.onMove(gestureState),
onPanResponderRelease: () => this.onEndMove()
});
onMove(gestureState: PanResponderGestureState) {
const {barHeight} = this.state;
const newDeltaValue = this.getValueFromBottomOffset(
-gestureState.dy,
barHeight,
min,
max
);
this.setState({
deltaValue: newDeltaValue
});
}
onEndMove() {
const {value, deltaValue} = this.state;
this.setState({value: value + deltaValue, deltaValue: 0});
}
capValueWithinRange = (value: number, range: number[]) => {
if (value < range[0]) return range[0];
if (value > range[1]) return range[1];
return value;
};
render() {
const {value, deltaValue, barHeight} = this.state;
const cappedValue = this.capValueWithinRange(value + deltaValue, [min, max]);
const bottomOffset = this.getBottomOffsetFromValue(
cappedValue,
min,
max,
barHeight
);
return (
//...
<Circle
bottomOffset={bottomOffset}
{...this.panResponder.panHandlers}
/>
//...
)
}
}
Hàm capValueWithinRange
được dùng để lấy giá trị của cái nút tròn so với độ cao của slider
Khi di chuyển marker, callback truyền cho onPanResponderMove
sẽ được gọi, nó nhận vào 2 giá trị
- native event: chứa những thuộc tính như vị trí user đã touch,...
- gestureState: đây là cái chúng ta đang dùng để truyền cho hàm
onMove
Tất cả giá trị của gestureState
Khi user buông tay ra, dừng sự kiện kéo rê, thì callback truyền cho hàm onPanResponderRelease
sẽ được gọi, cũng nhận tương tự 2 giá trị như trên: native event và gesture state
Xong, chủ yếu để làm cái này chúng ta chỉ cần nắm cách làm việc với PanResponder
trong react-native
Initializing...