안드로이드 수평계 앱 개발 (스마트 폰 가속도 센싱값 읽기)
본문 바로가기
IT기술 및 프로그래밍

안드로이드 수평계 앱 개발 (스마트 폰 가속도 센싱값 읽기)

by 리뷰하는 인간 2022. 6. 14.

안드로이드 앱 포스팅 전에 제가 마침내 수평계 앱을 출시하게 되었습니다. 이 앱은 제가 두 번째로 개발하게 된 앱입니다. 사실 이미 기존에 동종의 수많은 앱이 있지만 감을 익히기에도 좋고 개발이 쉬운 편이라고 생각합니다. 

 

출시된 제 앱의 링크 주소는 아래와 같습니다.

>>> 구글 플레이 앱링크 바로가기

 

간단 수평계 : Simlpe Level - Google Play 앱

사물의 수평을 쉽고 간편하게 측정할 수 있습니다.

play.google.com

 

제가하는 이 포스팅을 쭉 따라오시면 똑같은 앱을 개발하실 수 있습니다. 이제 이 앱의 개발을 지속해 보겠습니다. 

오늘은 네번째 시간으로 가속도 센서를 읽어서 디스플레이하는 기능을 해 보겠습니다.

지난 시간에 다루었던 세번째 GUI 개발 부분은 아래 링크를 따라가시면 됩니다.

>>> 안드로이드 수평계 앱개발 (GUI 만들기)

저번 시간까지 정리된 소스는 아래와 같습니다.

- MainActivity.java - 

package com.accel.accello_meter;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.FrameLayout;

public class MainActivity extends AppCompatActivity {
    Level_View lvl_view;//추가
    FrameLayout fL;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lvl_view = new Level_View(this);//추가

        fL = (FrameLayout)findViewById(R.id.sensorLayout) ;//추가
        fL.addView(lvl_view);
    }
}

 

- Level_View -

package com.accel.accello_meter;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.View;

public class Level_View  extends View {
    float  img_height;
    float img_width;
    Bitmap BackGround, BackGround_Line;
    Bitmap resizeImgBitmap;
    Bitmap resizeBackGround_Line;
    public Level_View(Context context) {
        super(context);
        Resources r = context.getResources();
        BackGround =  BitmapFactory.decodeResource(r, R.drawable.background);
        BackGround_Line =  BitmapFactory.decodeResource(r, R.drawable.background2);
        img_height= BackGround.getHeight();
        img_width = BackGround.getWidth();
    }

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.drawColor(Color.BLACK);
        // canvas.drawCircle(centerX,centerY,radius,wPaint);
        resizeImgBitmap = Bitmap.createScaledBitmap(BackGround, (int)(img_width), (int)(img_height), true); //사진이 클 경우, 다음과 같이 resize한다.
        resizeBackGround_Line = Bitmap.createScaledBitmap(BackGround_Line, (int)(img_width), (int)(img_height), true); //사진이 클 경우, 다음과 같이 resize한다.
        canvas.drawBitmap(resizeImgBitmap, 0, 0, null); //캔버스의 적절한 위치에 그린다.
        canvas.drawBitmap(resizeBackGround_Line, 0, 0, null);
        super.onDraw(canvas);
    }
}

 

-activity_main.xml-

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/sensorLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout_editor_absoluteX="1dp"
        tools:layout_editor_absoluteY="65dp">

    </FrameLayout>

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.Accello_Meter.AppBarOverlay"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Offset"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.05"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.023" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Reset"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.41"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.023" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="X axis : 0"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.377"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.567" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Y axis : 0"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.376"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.622" />




</androidx.constraintlayout.widget.ConstraintLayout>

 

위 세가지 파일이 수정된 부분입니다. 저번 시간까지는 이미지를 띄우는 기능까지 했지만 아직 이미지 해상도가 맞지 않습니다. 우선 가속도 센서를 간단하게 읽어서 표시하는 포스팅을 먼저 해보겠습니다.

우선 MainActivity.java파일을 아래와 같이 수정을 합니다.

package com.accel.accello_meter;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.FrameLayout;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements SensorEventListener {//  SensorEventListner 이벤트 인터페이스 추가

    Level_View lvl_view;
    FrameLayout fL;
    // 센서 매니저 선언 추가
    private SensorManager sensorManager;
    private Sensor accelerSensor;

    // 가속도 센서 저장 변수 선언 추가
    private float gAccX, gAccY, gAccZ;

    // 텍스트 뷰 선언
    TextView tvX, tvY;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //뷰 클래스
        lvl_view = new Level_View(this);

        //프레임 레이아웃에 뷰 클래스 추가
        fL = (FrameLayout)findViewById(R.id.sensorLayout) ;
        fL.addView(lvl_view);

        // 가속도 센서 관련 설정부분 추가
        sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        accelerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        tvX = (TextView)findViewById(R.id.textView);
        tvY = (TextView)findViewById(R.id.textView2);
    }
    //onResume 이벤트 추가
    @Override
    protected void onResume() {
        super.onResume();
        sensorManager.registerListener((SensorEventListener) this, accelerSensor, SensorManager.SENSOR_DELAY_GAME);


    }
    // onPause 이벤트 추가
    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener((SensorEventListener) this);
    }
    // onSensorChanged 추가
    @Override
    public void onSensorChanged(SensorEvent event)
    {
        if(event.sensor == accelerSensor)
        {
            // 가속도 센서 값 읽기
            gAccX = event.values[0];
            gAccY = event.values[1];
            gAccZ = event.values[2];

            // 가속도 센서 값 표시
            tvX.setText("X Value : " + String.format("%.1f", gAccX));
            tvY.setText("X Value : " + String.format("%.1f", gAccY) );
        }
        lvl_view.invalidate();
    }

    // onAccuracyChanged 추가
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {


    }

}

 

위 코드에서 가장 먼저 아래 SensorEventListener을 추가합니다. 인터페이스를 이용하여 구현하는 것으로 가속도 센서의 이벤트 처리를 위해 구현해 줍니다. 이해가 가지 않으시면 센서의 이벤트 값을 받아서 처리하기 위한 것이라고 편하게 받아들이세요.

public class MainActivity extends AppCompatActivity implements SensorEventListener

 

SensorManager를 선언하고 가속도 값을 저장할 변수를 선언합니다. 그리고 그 값을 표시할 TextView도 선언합니다.

// 센서 매니저 선언 추가
private SensorManager sensorManager;
private Sensor accelerSensor;

// 가속도 센서 저장 변수 선언 추가
private float gAccX, gAccY, gAccZ;

// 텍스트 뷰 선언
TextView tvX, tvY;

 

 

OnCreate 함수를 아래와 같이 수정합니다. 추가된 부분은 가속도 센서관련 설정 부분과 이를 표시하기 위해 TextView를 id 값으로 컨트롤을 연결하는 부분입니다.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //뷰 클래스
    lvl_view = new Level_View(this);

    //프레임 레이아웃에 뷰 클래스 추가
    fL = (FrameLayout)findViewById(R.id.sensorLayout) ;
    fL.addView(lvl_view);

    // 가속도 센서 관련 설정부분 추가
    sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
    accelerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    
    // R id를 활용하여 Textview 컨트롤 연결
    tvX = (TextView)findViewById(R.id.textView);
    tvY = (TextView)findViewById(R.id.textView2);
}

 

onResume함수에 Resume 이벤트 발생시 가속도 센서 처리하는 부분에 대한 구문을 아래와 같이 적어 줍니다.

//onResume 이벤트 추가
@Override
protected void onResume() {
    super.onResume();
    sensorManager.registerListener((SensorEventListener) this, accelerSensor, SensorManager.SENSOR_DELAY_GAME);
}

 

onPause 함수에 Pause 이벤트 발생시 가속도 센서 처리하는 부분에 대한 구문을 아래와 같이 적어 줍니다.

// onPause 이벤트 추가
@Override
protected void onPause() {
    super.onPause();
    sensorManager.unregisterListener((SensorEventListener) this);
}

 

아래의 OnSensorChanged함수는 가속도 센서의 값이 변경 될때 마다 이벤트가 발생하여 가속도 센서의 X, Y, Z 축 값을 읽어 들여 위에서 선언한 변수 gAccX, gAccY, gAccz에 저장합니다. 이후 저장된 값들 중 gAccx와 gAccY값을 textView에 각각 표시합니다.

@Override
public void onSensorChanged(SensorEvent event)
{
    if(event.sensor == accelerSensor)
    {
        // 가속도 센서 값 읽기
        gAccX = event.values[0];
        gAccY = event.values[1];
        gAccZ = event.values[2];

        // 가속도 센서 값 표시
        tvX.setText("X Value : " + String.format("%.1f", gAccX));
        tvY.setText("X Value : " + String.format("%.1f", gAccY) );
    }
    lvl_view.invalidate();
}

 

그리고 activity_main.xml 파일의 textview부분을 아래 코드를 추가합니다. 글자색을 바꾸어 줍니다.

android:textColor="@color/teal_200"

 

아래 코드에 추가된 부분을 확인해 주세요.

<TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="X axis : 0"
    android:textSize="24sp"
    android:textColor="@color/teal_200"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.377"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.567" />

<TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Y axis : 0"
    android:textSize="24sp"
    android:textColor="@color/teal_200"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.376"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.622" />

 

이제 실행하여 상태를 확인해 보겠습니다.

디버깅을 하여 스마트 기기 또는 가상 시뮬레이터에서 지금까지 결과를 확인 해 봅니다.

저는 가상 디바이스를 활용했습니다. 우선 아래 그림처럼 실행이 되고 X Value와 Y Value 값이 아래 그림처럼 나타남을 확인합니다. 그리고 우측의 회전 버튼을 한 번씩 눌러 주면 센서의 값이 바뀌는 것을 확인할 수 있습니다.

 

안드로이드 가속도 센서 결과 확인
안드로이드 가속도 센서 결과 확인

하지만 좀 더 다이내믹한 결과를 확인하고 싶으시면 실제 기기에 다운로드하여 확인해서 기기를 이리저리 움직이면 확실히 센서의 값이 바뀌는 것을 확인할 수 있습니다.

아직까지는 이 센서의 값은 Raw 값이며 가공을 하여 실제 적용 가능한 각도 값으로 변환할 것입니다. 

지금은 소수점 첫째 자리까지 표현했지만 셋째짜리 까지 표현하는 경우 가만히 놓아두어도 상당히 값들이 산만하게 표시가 되는 것을 확인할 수 있습니다.

실제 현재 업데이트되는 Raw 값을 이용해서는 무리가 있습니다. 다음 시간에는 이 데이터를 가공하는 방법에 대해 포스팅합니다.

도움이 되셨다면 댓글과 공감 부탁드립니다. 

댓글


TOP

TEL. 02.1234.5678 / 경기 성남시 분당구 판교역로