React Native – Sử dụng các cảm biến (sensor)

0
246
Bài này thuộc phần 3 của 9 phần trong series React Native Training cho người mới

React Native - Sử dụng các cảm biến (sensor)

Nhiều bạn khi mới học về React Native thường hay thắc mắc: Liệu React Native có giống với các framework lập trình cross-platform khác không? Liệu nó có thể can thiệp sâu vào hệ thống phần cứng được không?

Đừng lo! Đúng như cái tên của nó: React Native. Nghĩa là nó rất gần với các app native rồi.

Một trong những điểm mạnh thú vị nhất của React Native là cho phép bạn kết nối giữa code native với code Javascript. Nghĩa là, bạn có thể can thiệp rất sâu vào hệ thống thông qua các API native. Ví dụ: bạn có thể code Java/Kotlin và sử dụng API trực tiếp của Android trong dự án React native. Tương tự cho code Ios cũng vậy.

Quá tuyệt đúng không!

Đôi điều về thư viện React Native Sensor

Thật khó để so sánh cộng đồng React Native với các cộng đồng khác. Theo đúng nghĩa đen thì đây là cộng đồng mà thành viên đến từ đủ mọi “tầng lớp xã hội”. Trong đó, có những người xuất thân là web developer người mà giỏi javascript và hứng thú với với ứng dụng mobile. Hay những bạn trước đó đã có kinh nghiệm với ReactJs thì nay nghe nói đến React Native mà nhảy vào thôi…

Như mình đã nói ở trên thì React Native có thể can thiệp sâu vào phần cứng thông qua API native của nền tảng. Thông thường chúng chỉ là iOS hoặc Android. Điều này khiến developer cần phải chọn nhiều thư viện để thực hiện cùng một công việc cho các platform khác nhau. Nó làm mất đi tính chất cross-platform(Lập trình đa nền tảng- code một lần cho mọi nền tảng)

Một vấn đề khác là API của các native module. Một số module rất tốt, nhưng một số module khác chỉ đơn giản là sử dụng các nguyên thủy (primitive)  API mà React Native cung cấp, chẳng hạn như NativeEventEmitter.

Khi mình phát triển tham gia dự án React Native cần đến các sensor để phát triển các tính năng và mình đã gặp cả 2 vấn đề nêu trên. Thế là mình hùng hục tìm kiếm trên mạng xem có giải pháp nào nó tối ưu không? Xem có module nào mà wraper cho cả 2 nền tảng Android và Ios mà không phải code riêng rẽ. Thật là may mắn là mình đã tìm ra, đó là react-native-sensors.

Dành cho bạn: Miễn phí cuốn sách: Making Money with Apps on the Android Market

Sử dụng React Native Sensor như thế nào?

react-native-sensors cung cấp interface dựa trên RxJS cho Accelerometer, Gyroscope và Magnetometer. API của nó phù hợp với các platform và các loại sensor khác nhau. Tất nhiên là thư viện này rất dễ sử dụng. Bạn theo dõi đoạn code bên dưới:

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View
} from 'react-native';
import { Accelerometer } from "react-native-sensors";

const Value = ({name, value}) => (
  <View style={styles.valueContainer}>
    <Text style={styles.valueName}>{name}:</Text>
    <Text style={styles.valueValue}>{new String(value).substr(0, 8)}</Text>
  </View>
)

export default class App extends Component {
  constructor(props) {
    super(props);
    // line 20
    new Accelerometer({
      updateInterval: 400 // defaults to 100ms
    })
      .then(observable => {
        observable.subscribe(({x,y,z}) => this.setState({x,y,z}));
      })
      .catch(error => {
        console.log("The sensor is not available");
      });
      
    this.state = {x: 0, y: 0, z: 0};
  }
  // line 33
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.headline}>
          Accelerometer values
        </Text>
        <Value name="x" value={this.state.x} />
        <Value name="y" value={this.state.y} />
        <Value name="z" value={this.state.z} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  headline: {
    fontSize: 30,
    textAlign: 'center',
    margin: 10,
  },
  valueContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  valueValue: {
    width: 200,
    fontSize: 20
  },
  valueName: {
    width: 50,
    fontSize: 20,
    fontWeight: 'bold'
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

Mình giải thích một chút:

  • Dòng 20: Khởi tạo một Accelerometer mới. Hàm này nó trả về một promise. Promise sẽ hoàn thành khi sensor đã available, có thể sử dụng. Ok, Sensor đã ready thì ta sẽ đăng kí observable và thiết lập state.
  • Dòng 33: Ở hàm render, chúng ta chỉ đơn giản là access vào state để hiển thị dữ liệu thô của sensor. Kiểu như bên dưới này:

react-native-su-dung-cac-cam-bien-sensor-2

Thực hành với project

Để minh họa cho các tính năng của thư viện này,mình tạo một project đơn giản: Mình muốn sử dụng sensor con quay hồi chuyển (gyroscope) để cho phép người dùng tương tác với một hình ảnh mà nó quá rộng để hiển thị trên toàn bộ màn hình. Thay vì vuốt sang trái hoặc sang phải, chỉ cần xoay điện thoại thì mình sẽ nhìn thấy các phần khác của hình ảnh.

import React, { Component } from "react";
import { StyleSheet, Text, View, Image } from "react-native";
import { Gyroscope } from "react-native-sensors";
const Dimensions = require("Dimensions");
const PixelRatio = require("PixelRatio");
const window = Dimensions.get("window");

const deviceWidth = window.width;
const deviceHeight = window.height;

const imageWidth = 8 * deviceWidth;
const imageHeight = deviceHeight;

const middleOfTheScreenX = (imageWidth - deviceWidth) / 2;

export default class App extends Component {
  constructor(props) {
    super(props);

    new Gyroscope({
      updateInterval: 50
    })
      .then(observable => {
        observable.subscribe(({ y }) => {
          this.setState(state => ({
            y: y + state.y
          }));
        });
      })
      .catch(error => {
        console.log("The sensor is not available");
      });

    this.state = {
      image: `https://placeimg.com/${PixelRatio.getPixelSizeForLayoutSize(
        imageWidth
      )}/${PixelRatio.getPixelSizeForLayoutSize(imageHeight)}/any`,
      y: 0
    };
  }

  render() {
    const positionOnScreenX = -imageWidth / 2;
    // The y axis of the sensor data resembles what we need for the x axis
    // in the image
    const movementX = -this.state.y / 10 * imageWidth;

    return (
      <View style={styles.container}>
        <Image
          translateX={positionOnScreenX + movementX}
          style={styles.image}
          source={{ uri: this.state.image }}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF"
  },
  image: {
    position: "absolute",
    top: 0,
    left: 0,
    height: imageHeight,
    width: imageWidth
  }
});

Để tính toán xem mình sẽ di chuyển hình ảnh bao nhiêu, mình chỉ cần thêm các giá trị cảm biến cho tọa độ y. Như vậy nếu bạn để điện thoại nghiêng sang trái, bạn sẽ tiếp tục ở bên trái của hình ảnh.

Trong hàm render, mình sử dụng translateX để định vị hình ảnh ở giữa màn hình và sau đó thêm độ lệch được tính toán để xem dữ liệu cảm biến của chúng ta sẽ chuyển chúng ta đến đâu. Bằng cách chia giá trị cảm biến cho 10 mình có thể làm cho chuyển động trơn tru hơn. Bạn có thể thử với giá trị này để xem nó ảnh hưởng đến trạng thái như thế nào.

Sự khác biệt duy nhất trong việc xây dựng cảm biến mà mình tạo ra là mình đã thêm khoảng thời gian cập nhật cao hơn để tạo ra cảm giác uyển chuyển hơn khi sử dụng các harsh motion.

Và đây là kết quả cuối cùng:

Kết luận

Như vậy là đã hoàn thành dự án, bạn thử build và chạy trên cả Android và Iphone xem nhé. Ngoài ra, bạn có thể download toàn bộ source code của project mẫu ở trên

Dự án của bạn có sử dụng đến sensor không? Rất muốn nghe ý kiến từ các bạn, đừng ngần ngại mà để comment ở bên dưới nhé

Xem tiếp các bài trong Series
Phần trước: React Native – Cách Debug chương trình trong Visual CodePhần kế tiếp: React Native – Phân biệt Props và State cực kỳ đơn giản và dễ hiểu

Bình luận. Đặt câu hỏi cũng là một cách học

avatar
  Theo dõi bình luận  
Thông báo