안드로이드개발 SensorManager, SensorEventListener 이용해서 나침판 구현하기

 

환경: Android Studio

 

나침판을 구현하기 위해서 클래스를 두 부분으로 나누었습니다. 자석계 Sensor 로 부터 값을 가져오는 메인 activity 와 나침판을 그리는 View 클래스가 그것 입니다. View 위젯을 상속받은 CustomDrawableView 클래스는 나침판을 그리고 azimut 값을 이용해서 실시간 방위를 나타냅니다.

 

CustomDrawableView 클래스의 onDraw() 함수에 그리는 소스들이 들어갑니다. 움직일때 마다 나침판 효과를 내기 위해 Canvas rotate() 함수로 방위각을 구해 회전을 시킵니다. 그럼 센스에서 받아 온 실시간 데이터로 계속해서 움직이겠죠. 그리고 회전한 Canvas 위에 나침판의 남북 선을 긋습니다. 

// 방위각 구하기 
dir = azimuth * 360 / (2 * 3.14159f);

// Canvas 를 회전 시킨다 
if (azimuth != null){
	canvas.rotate(-dir, centerx, centery);
}

// 나침판 선 표시 
paint.setColor(Color.BLUE);
paint.setTextSize(100);
canvas.drawLine(centerx, centery - radius, centerx, centery + radius, paint);
canvas.drawText("N", centerx - 30, centery - radius - 20, paint);
canvas.drawText("S", centerx - 30, centery + radius + 80, paint);
paint.setColor(Color.RED);

 

스마트폰 Sensor 로 부터 방위각을 구하기 위해 onCreate() 에서 가속계와 자석계 센스를 SensorManager 로부터 객체를 가져옵니다. 그리고 센서의 값이 변경될 때 마다 변경된 값을 받기 위해 onResume() 함수에 이벤트 리스너를 등록합니다.

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	mCustomDrawableView = new CustomDrawableView(this);
	setContentView(mCustomDrawableView); // Register the sensor listeners
	mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
	accelerometer = mSensorManager
			.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
	magnetometer = mSensorManager
			.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}

protected void onResume() {
	super.onResume();
	mSensorManager.registerListener(this, accelerometer,
			SensorManager.SENSOR_DELAY_UI);
	mSensorManager.registerListener(this, magnetometer,
			SensorManager.SENSOR_DELAY_UI);
}

 

등록한 이벤트 리스너에서 센스에 변경이 있을 때 작동하는 함수는 onSensorChanged 입니다. SensorEvent 객체를 분석해서 3가지 azimuth, pitch, roll 값을 얻을 수 있습니다. 3가지 값 중 첫 번째 배열에 있는 값이 azimuth (방위각) 입니다. 이것을 View 클래스에서 가져다 쓰는 것입니다.

public void onSensorChanged(SensorEvent event) {
	if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
		mGravity = event.values;
	}
	if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD){
		mGeomagnetic = event.values;
	}
	if (mGravity != null && mGeomagnetic != null) {
		float R[] = new float[9];
		float I[] = new float[9];
		boolean success = SensorManager.getRotationMatrix(R, I,
				mGravity, mGeomagnetic);
		if (success) {
			float orientation[] = new float[3];
			SensorManager.getOrientation(R, orientation);
			azimuth = orientation[0]; // orientation contains: azimut, pitch and roll
		}
	}
	mCustomDrawableView.invalidate();
}

 

아래 샘플 소스는 View Activity 가 합쳐 진 전체 소스입니다. 화면 레이아웃을 구성하는 xml 파일은 없습니다.

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;

public class CompassActivity2 extends Activity implements
		SensorEventListener {

	private CustomDrawableView mCustomDrawableView;
	private SensorManager mSensorManager;
	private Sensor accelerometer;
	private Sensor magnetometer;
	private float[] mGravity;
	private float[] mGeomagnetic;
	
	private Float azimut; // View to draw a compass

	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mCustomDrawableView = new CustomDrawableView(this);
		setContentView(mCustomDrawableView); // Register the sensor listeners
		mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
		accelerometer = mSensorManager
				.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		magnetometer = mSensorManager
				.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
	}

	protected void onResume() {
		super.onResume();
		mSensorManager.registerListener(this, accelerometer,
				SensorManager.SENSOR_DELAY_UI);
		mSensorManager.registerListener(this, magnetometer,
				SensorManager.SENSOR_DELAY_UI);
	}

	protected void onPause() {
		super.onPause();
		mSensorManager.unregisterListener(this);
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	public void onSensorChanged(SensorEvent event) {
		if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
			mGravity = event.values;
		}
		if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD){
			mGeomagnetic = event.values;
		}
		if (mGravity != null && mGeomagnetic != null) {
			float R[] = new float[9];
			float I[] = new float[9];
			boolean success = SensorManager.getRotationMatrix(R, I,
					mGravity, mGeomagnetic);
			if (success) {
				float orientation[] = new float[3];
				SensorManager.getOrientation(R, orientation);
				azimut = orientation[0]; // orientation contains: azimut, pitch and roll
			}
		}
		mCustomDrawableView.invalidate();
	}
	
	public class CustomDrawableView extends View {
		Paint paint = new Paint();

		public CustomDrawableView(Context context) {
			super(context);
			paint.setColor(Color.RED);
			paint.setStyle(Style.STROKE);
			paint.setStrokeWidth(4);
			paint.setAntiAlias(true);
		};

		protected void onDraw(Canvas canvas) {
			int width = getWidth();
			int height = getHeight();
			int centerx = width / 2;
			int centery = height / 2;
			float radius, dir;

			if (centerx > centery) {
				radius = (float) (centery * 0.9);
			} else {
				radius = (float) (centerx * 0.9);
			}
			
			// 방위각 구하기 
			dir = azimut * 360 / (2 * 3.14159f);
			
			// 나침판 그리기 
			canvas.drawCircle(centerx, centery, radius, paint);
			canvas.drawLine(centerx, centery - radius, centerx, centery + radius, paint);
			canvas.drawLine(centerx - radius, centery, centerx + radius, centery, paint);
			
			// 방위 중앙에 표시 
			paint.setColor(Color.DKGRAY);
			paint.setTextSize(300);
			canvas.drawText(String.valueOf((int)dir), centerx-150, centery, paint);
			
			// Canvas 를 회전 시킨다 
			if (azimut != null){
				canvas.rotate(-dir, centerx, centery);
			}
						
			// 나침판 선 표시 
			paint.setColor(Color.BLUE);
			paint.setTextSize(100);
			canvas.drawLine(centerx, centery - radius, centerx, centery + radius, paint);
			canvas.drawText("N", centerx - 30, centery - radius - 20, paint);
			canvas.drawText("S", centerx - 30, centery + radius + 80, paint);
			paint.setColor(Color.RED);
		}
	}
}

 

안드로이드(Android) SensorManager, SensorEventListener 이용해서 나침판 구현하기

 

Posted by 녹두장군