Home Công nghệ Tạo slider component trong React Native bằng PanResponder

Tạo slider component trong React Native bằng PanResponder

2355
Bài viết được sự cho phép của tác giả Lưu Bình An
Tuts này sẽ hướng dẫn các bạn tạo một slider component đơn giản trong React Native bằng PanResponder
Đây là cái chúng ta sẽ tạo

  Giải thích React Component Lifecycle
  Giới thiệu Higher-Order Component trong React

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

Bài viết gốc được đăng tải tại VuiLapTrinh

Có thể bạn quan tâm:

Xem thêm các việc làm React Native hấp dẫn tại TopDev