안드로이드 어플리케이션이 회전될 때 주의해야할 점

댓글수0 다음블로그 이동

IT_Programming/Android_Java

안드로이드 어플리케이션이 회전될 때 주의해야할 점

JJun ™
댓글수0



 출처: http://techlog.gurucat.net/37



Android One Screen Turn Deserves Another



[이 포스트는 오픈소스와 호화성 프로그램 매니저인 Dan Morrill에 의해 작성되었습니다. — Tim Bray]


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

그러나, 최근 이와 관련된 새로운 문제가 발견되었습니다. 새로 출시된 디바이스들 중에는 (모토롤라의 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 에서 사용되는 좌표 공간은 항상 디바이스의 물리적인 윗면과 옆면을 기준으로 설정됩니다.
따라서, 사용자가 휴대폰을 가로로 쥐어 화면 좌표계가 변경되는 경우에 센서의 좌표계와 화면 좌표계는 일치하지 않게 됩니다.
그 결과 여러분의 어플리케이션은 잘못된 방향으로 화살표를 표시하게 됩니다. 아래 그림과 같이 말입니다.


 

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

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

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


올바른 해결책

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

아니요. 사실은 방법이 있습니다. 여러분은 필요에 따라 그래픽을 적절히 회전하도록 구현할 수 있습니다.
단지 필요한 것은 언제 이러한 화면 보정이 필요한지 정확하게 판단하는 방법입니다.
그럼 어떻게 디바이스의 화면 좌표계가 변경되어 화면을 보정할 필요가 있는지 알 수 있을까요?
정답은 바로 
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() 메서드를 사용할 것을 권장합니다.)


적용 방법

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


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

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


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

표준적인 도구를 사용하는 대부분의 안드로이드 어플리케이션은 포트레이트 모드와 랜드스케이프 모드 양쪽 다에서 정상적으로 동작합니다.
만일 여러분의 두 가지 모드를 모두 지원하며, 센서도 활용한다면, 제가 위에 언급했던 몇 가지 주의 점이 있습니다.
  • 포트레이트 모드를 기본 모드로 가정하지 마세요
  • 어플리케이션을 포트레이트 모드로 고정한다고 문제가 해결될 것이라고 가정하지 마세요
  • 센서로 인한 화면 회전 기능을 꺼놓는다고 문제가 해결될 것이라고 가정하지 마세요.
    (특정 디바이스는 하드웨어 키보드로 인해 화면 회전이 일어날 수 있음으로)
  • 현재 디바이스의 오리엔테이션을 getRotation() 메서드를 이용하여 확인하고, 이를 이용하여 그래픽을 적절히 회전시키세요.


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

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


휴~ 알고보면 간단한 문제인데 말이 길었네요. 한 문장으로 요약하겠습니다.

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

이 포스팅이 여러분에게 유용하면 좋겠습니다. 아니 실재적인 쓸모가 있으면 좋겠네요.
안드로이드 개발팀은 안드로이드 SDK 와 다큐먼트를 개속 개선해 나갈 것 입니다. 여러분은 여러분의 어플리케이션을 개선해주세요. 
즐거운 코딩 되시길.


맨위로

https://blog.daum.net/creazier/15310454

신고하기