제임스딘딘의
Tech & Life

개발자의 기록 노트/Android

[안드로이드] 안드로이드 화면이 회전될 때 어플리케이션 개발자가 주의해야할 점

제임스-딘딘 2010. 12. 25. 12:45

안드로이드 화면이 회전될 때 어플리케이션 개발자가 주의해야할 점

 

 안드로이드는 가속도 센서나  조도 센서와 같은 다양한 종류의 센서를 다루기 위한 API 를 제공하고 있다. 그 중 가장 일반적으로 사용되는 센서 두 가지는, 가속도 센서와 자기장 센서(나침반 센서) 이다. 어플리케이션과 디바이스는 이 두 가지 센서를 사용자 입력값으로 활용하여, 디바이스 스크린을 어느 방향으로 돌릴지 결정하는데 사용하곤 한다.

 그러나, 최근 이와 관련된 새로운 문제가 발견되었다. 새로 출시된 디바이스들 중에는 (모토롤라의 CHARM 과 FLIPOUT 이라는 모델) 기본 화면 모드로 포트레이트(portrait) 모드 대신 랜드스케이프(landscape) 모드를 사용하고 있다. 따라서, 기본 화면이 세로로 길쭉하지 않고 가로로 넓다. 테스트해본 결과 몇몇 어플리케이션에서 이에 따른 문제가 발생하는 것이 확인되었다.


기본 화면 모드로 Landscape를 채택한 Charm과 Flipout


 이 문제는, 안드로이드 SDK의 센서 API에 관한 문서 중에 누락된 내용이 몇몇 있었고(두둥...), 따라서 어플리케이션 개발자들이 잘못된 방식으로 해당 API를 사용한 점에 원인이 있었다. 더군다나, 안드로이드 개발팀에서 직접 작성한 몇몇 예제에서도 동일한 문제가 발견되었다. 

다행스럽게도, 이 API들을 올바르게 사용하는 것은 어렵지 않다. 다음의 세가지 규칙을 기억하면 된다.

  • 센서 API에서 사용되는 디바이스의 기본 오리엔테이션에 대한 좌표계(Coordinate System)는 OpenGL 좌표계와 동일하며, 디바이스가 회전되는 경우에도 변하지 않는다.
  • 어플리케이션은 디바이스의 기본 오리엔테이션이 포트레이트 모드라고 가정해서는 안된다.
  • 센서 값을 화면과 매칭하여 사용하는 어플리케이션은, 메니페스트 상에 디스플레이 모드로 포트레이트 모드만을 사용한다고 명시한 경우에도, android.view.Display.getRotation() 메서드를 이용하여 센서값의 좌표계와 화면 좌표계를 동일하게 일치시켜야 한다.

 만일 여러분이 수학에 강하다면, 위의 세가지 규칙이 뜻하는 바를 단번에 이해할 수 있을 것이다. 다음에 이어질 내용을 건너뛰어도 좋다. 그렇지 않더라도 너무 걱정정할 필요는 없다. 이어질 내용을 통해 각각의 규칙에 관하여 하나 하나 설명하고, 센서를 올바르게 사용하기 위한 몇 가지 팁들을 알아보겠다.


기본 사항

 시작하기에 앞서 도움이 될만한 팁이 하나 있다. '센서 값의 좌표계는 결코 변하지 않는다' 라는 사실을 기억하자. 이어지는 포스트에서는 좌표계와 회전 그리고 기타 등등에 관하여 이야기한다. 때때로 3D 트랜스폼에 관하여 깊이 생각하다 보면 여러가지 혼란에 빠질 수도 있다. 그럴 경우에 화면에 어떤 일이 생기던지 센서에서 사용되는 좌표계는 결코 변하지 않는다고 자꾸 되새기는 것이 큰 도움이 될 수 있다.

 자, 그럼 한 가지 예를 들어보겠다. 항상 화살표가 땅쪽을 가르키도록 구현된 간단한 어플리케이션을 생각해 보자. 휴대폰을 일반적인 방법으로 손에 쥔 경우, 화살표는 그림 처럼 아래쪽을 가리키고 있을 것이다.


 

노트: 이 그림에서, G 는 센서 좌푝계에서의 중력 방향을 의미한다. 예를 들어 위 그림에서 "G = -y" 가 의미하는 것은 중력 방향은 가속도 센서에 의해 측정된 디바이스의 -Y 축 방향과 일치한다는 뜻 이다. 그리고 기억하자. 센서의 좌표계는 결코 변하지 않는다!

 OpenGL 을 이용하여 위와 같은 어플리케이션을 만드는 것은 매우 간단하다. 가속도 센서에 의해 측정된 값을 이용하여 좌표 공간을 적절히 회전 한 후, GLSurfaceView 상에 화살표를 그리면 된다. 일반적인 경우라면 이 방법은 "동작" 하는 것으로 보여질 것이다. 'OpenGL 화면의 좌표계와 센서의 좌표계가 일치하는 한' 말이다.

하지만 사용자가 휴대폰을 돌려 보면 문제가 발생한다.


뭐가 문제일까?

 대부분의 안드로이드 디바이스는 가속도센서를 이용하여 사용자가 가로로 디바이스를 드는 경우를 감지하여 알려주며, 이에 맞추어 화면을 회전한다. 덕분에, 어플리케이션은 사용자의 시선과 수평되게 화면에 보여질 수 있다. 

  화면이 회전되는 경우, 어플리케이션이 자동으로 가로 세로가 변경되어 그려질 수 있도록, X축과 Y축은 서로 뒤바뀌게 됩니다. 그러나 안드로이드의 센서 API 에서 사용되는 좌표 공간은 항상 디바이스의 물리적인 윗면과 옆면을 기준으로 설정된다. 따라서, 사용자가 휴대폰을 가로로 쥐어 화면 좌표계가 변경되는 경우에 센서의 좌표계와 화면 좌표계는 일치하지 않게 된다. 그결과 여러분의 어플리케이션은 잘못된 방향으로 화살표를 표시하게 될 것이다. 아래 그림과 같이 말이다.

 

 

 이러한 좌표계 불일치 문제를 해결하기 위해 널리 사용되는 몇 가지 방법이 있다. 하지만, 안드로이드 개발팀이 확인한 결과, 이 중 몇몇 방식은 랜드스캐이프(landscape) 모드가 기본인 디바이스 상에서는 올바르게 동작하지 않는다.

 가장 일반적인 해결 방안은 메니페스트 파일상에서 android:screenOrientation 속성 값을 설정하여, 어플리케이션의 화면 모드를 포트레이트(portrait) 모드로 고정하는 것이다. 이런 경우, 시스템은 디바이스가 회전되는 경우에도 화면 좌표계를 재설정 하지 않으며, 센서 좌표계와 화면 좌표계는 계속 일치하게 된다. 하지만, 이 방식은 포트레이트 모드가 기본 모드인 디바이스에서는 한 가지 해결책이 될 수 있지만, 랜드스케이프 모드가 기본 화면 모드인 디바이스에서는 문제가 발생할 수 있다. 센서 좌표계는 랜드스케이프 모드를 기준으로 설정되는 반면에, 포트레이트 모드를 유지하기 위하여, 화면 좌표계가 변경되기 때문이다.

설정 가능한 android:screenOrientation 속성 값들



 두 번째로 일반적인 기법은 디바이스가 랜드스케이프 모드로 변경되는 이벤트를 알아채고, 이에 따라 화면에 그려지는 그래픽을 적절히 회전해주는 방법이다. 하지만, 불행이도 이는 완벽한 해결책이 될 수 없다. 랜드스케이프 모드를 탐지하는데 주의를 기울이지 않을 경우, 랜드스케이프가 기본 화면 모드인 디바이스 상에서 불필요하게 화면상의 그래픽을 회전시켜주는 실수를 범하기 쉽다.


올바른 해결책

 그럼 어떻게 해야할까? 진퇴양난에 빠진 것 같다. 화면 오리엔테이션 변경을 막는 것도 안되고, 강제로 화면을 회전하는 것도 할 수 없는 것 처럼 보인다.

그러나, 사실은 방법이 있다. 여러분은 필요에 따라 그래픽을 적절히 회전하도록 구현할 수 있다. 단지 이 구현에 필요한 것은 '언제 이러한 화면 보정이 필요한지를 정확하게 판단하는 방법'이다. 그럼 어떻게 디바이스의 화면 좌표계가 변경되어 화면을 보정할 필요가 있는지 알 수 있을까? 정답은 바로 android.view.Display.getRotation() 메서드를 활용하는 것 이다.


android.view.Display.getRotation() 메서드

 이 메서드는 화면 좌표계가 변경되었는지 그렇지 않았는지에 관하여 네 가지 값 중 하나를 반환한다. 만일 화면 좌표계가 전혀 변하지 않았을 경우에는 ROTATION_0 값을, 그 외에 90도, 180도, 270도 회전 되었을 경우에 따라, 각각 ROTATION_90, ROTATION_180,  ROTATION_270 값을 반환한다.

 마지막 두 가지 값 ROTATION_180 과 ROTATION_270 을 주의깊게 살펴보아야 한다. 이는 디바이스 상에는 실제로 두 가지 종류의 포트레이트 모드와 랜드스케이프 모드 - 기본 버전과 뒤짚힌 버전 가 있다는 점을 의미한다. 안드로이드 디바이스중에는 이처럼 서로 다른 4종류의 화면 모드를 사용하는 디바이스가 있을 수 있다. 따라서 여러분은 단순하게 포트레이트 모드와 랜드스케이프 모드를 처리하는 것이 아니라, 이 네가지 모드를 모두 올바르게 처리해 두어야 한다.

 화면 오리엔테이션 정보를 알고나면, 여러분이 화면에 그래픽을 그릴 때, 화면 오리엔테이션 정보를 마치 화면의 Z 축이 회전된 것으로 생각할 수 있다. 즉, SensorEventListener로부터 넘겨 받은 값에 화면 오리엔테이션 정보를 기반으로 한 'Z축 회전'을 적용하면, 모든 디바이스에서 올바르게 그리고 안정적으로 동작하는 어플리케이션을 작성할 수 있다.

 한가지 노트. Display.getRotation()는 디바이스 스크린이 회전 되었는지 그렇지 않은지에 관해 알려주는 메서드이기 때문에, android:screenOrientation = "nosensor"값을 설정하여, 가속도 센서로 인해 화면이 회전되는 경우가 아닌 경우, 예를 들어 사용자가 하드웨어 키보드를 사용하는 경우에도 화면의 회전 여부를 확인 할 수 있다.  

 이러한 문제를 해결하는 일에는 몇 가지 수학적 연산이 필요하기 때문에 조금 귀찮은 일이 될 수 있다. 따라서, 안드로이드 개발팀에서는 편리한 SensorManager.remapCoordinateSystem(메서드를 제공한다. 이 메서드를 사용하지 않는 경우에는, 어림 짐작으로 2개의 축을 뒤바꾸는 방식으로 유사한 효과를 볼 수 있다. (하지만 이는 오류가 발생할수 있기 때문에 별다른 이유가 없는한 remapCoordinateSystem() 메서드를 사용할 것을 권장한다.)


SensorManager.remapCoordinateSystem( ) 메서드


적용 방법

 좋다. 이제 여러분은 모든 종류의 디바이스에서 다 잘 동작하는 해결책을 알게 되었다. 하지만 여러분의 어플리케이션을 어떻게 업데이트 할 수 있을지 생각해보자. 어플리케이션을 수정하는 방법에  대하여 좀 더 명시적인 도움을 주기 위하여, 어플리케이션의 종류에 따라 적용할 수 있는 몇 가지 레시피를 마련해 보았다.


센서 데이터를 이용하여 그림을 그리지 않는 어플리케이션의 경우

 센서 데이터를 이용하여 그래픽을 표시하는 작업을 수행하지 않는 어플리케이션은 그다지 수정할 내용이 없다. 가속도 센서를 이용하여 디바이스가 어딘가에 부딪히는 경우를 인식하고, 이를 하나의 제스처 입력으로 활용하는 어플리케이션 같은 경우를 예로 들 수 있겠다.


포트레이트 모드와 랜드스캐이프 모드를 모두 지원하는 경우

 표준적인 도구를 사용하는 대부분의 안드로이드 어플리케이션은 포트레이트 모드와 랜드스캐이프 모드 둘 다 정상적으로 동작한다. 만일 여러분의 두 가지 모드를 모두 지원하며, 센서도 활용한다면, 위에 언급했던 몇 가지 주의 점을 다시 상기하자.

  • 포트레이트 모드를 기본 모드로 가정하지 말 것.
  • 어플리케이션을 포트레이트 모드로 고정한다고 문제가 해결될 것이라고 가정하지 말 것.
  • 센서로 인한 화면 회전 기능을 꺼놓는다고 문제가 해결될 것이라고 가정하지 말 것. (특정 디바이스는 하드웨어 키보드로 인해 화면 회전이 일어날 수 있으므로..)
  • 현재 디바이스의 오리엔테이션을 getRotation() 메서드를 이용하여 확인하고, 이를 이용하여 그래픽을 적절히 회전시킬 것.

오직 한가지 오리엔테이션에서만 지원 하는 경우

 게임과 같은 어플리케이션은 포트레이트 혹은 랜드스케이프 모드 중 한 가지 모드만을 지원하는 경우가 많다. 하지만 이 경우에도 주의할 점이 있다. 안드로이드 디바이스는 실제로 두 종류의 랜드스케이프 모드와 포트레이트 모드를 지원하기 때문에 현재의 화면 오리엔테이션을 확인 할 필요가 있다. 또한, 만일 어떤 어플리케이션이 랜드스케이프 모드에서만 동작한다면 포트레이트가 기본인 디바이스에서는 화면 보정이 필요하고, 그렇지 않은 경우에는 화면을 보정할 필요가 없다. 그리고 물론 - 이미 귀에 딱지가 앉았을 것 같지만 - 이는 getRotation() 메서드를 이용해서 구현 할 수 있다.


알고보면 간단한 문제인데 설명이 장황했다. 한 문장으로 요약하겠다.

 android.view.Display.getRotation() 은 여러분의 친구이다.

 이 포스팅이 여러분에게 유용하면 좋겠다. 아니 실질적인 도움이 되었으면 좋겠다. 여러분의 어플리케이션에 문제가 있다면 개선을 했으면 좋겠다. 즐거운 코딩 되시길.