보통 화면의 회전을 처리하는 경우는 화면이 portrait인지 landscape인지 여부이다. 그러나, 나침반처럼 회전방향에 따라 바늘 각도를 다르게 계산해야 하는 경우라면, 4방향의 회전방향을 모두 알아야한다. 여기서는 다루지 않지만 사족을 달자면, 나침반은 화면이 뒤집혀 좌표계가 뒤집히는 경우까지 고려해야 한다.
화면의 현재 회전상태를 알아내는 방법
화면의 회전은 UI영역이고, Activity에서 처리해야 함을 유추할 수 있다. Fragment가 아니라 Activity인 이유는, Fragment가 개별적으로 rotation이 되지 않기 때문이다. 순간 더블스크린에 하나만 가로모드로 돌아가는 예전 LG 윙스가 생각나지만 ㅋㅋㅋ 아마 별도의 Activity로 처리했겠지.
Portrait / Landscape
먼저, 현재 상태를 알아내는 방법을 살펴보자. portrait/landscape 여부는 Activity에서 다음을 이용해서 체크 가능하다.
when(resources.configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> {}
Configuration.ORIENTATION_LANDSCAPE -> {}
else -> {} // Configuration.ORIENTATION_UNDEFINED
}
Activity말고 application의 context를 이용하면 안될까 생각할 수 있는데, getResources()가 Android R(11) 부터 멀티윈도우나 세컨드 윈도우의 값이 줄수도 있어 Activity(정확히는 WindowContext)를 이용해야 한다고 나온다. 해당 가이드문서를 인용하면 다음과 같이 되어있다.
After
Build.VERSION_CODES#R
,Resources
must be obtained byActivity
orContext
created withContext.createWindowContext(int, Bundle)
.Application#getResources()
may report wrong values in multi-window or on secondary displays.
Rotation 0, 90, 180, 270 degree
화면의 4방향 회전을 알아내려면, DisplayManager를 이용하며 코드는 다음과 같다.
val display = getSystemService<DisplayManager>()?.getDisplay(Display.DEFAULT_DISPLAY)
if (display != null) {
when(display.rotation) {
Surface.ROTATION_0 -> { }
Surface.ROTATION_90 -> { }
Surface.ROTATION_180 -> { }
Surface.ROTATION_270 -> { }
}
}
Window가 아니고 Display로부터 얻어오는 것인데, Display.DEFAULT_DISPLAY를 사용하고 있다. 만약에 다중 디스플레이가 존재한다면 이부분을 고려해야 하겠지만, 보통은 이걸로 충분하겠지. 이제, 동적으로 변경되는 시점을 잡아내서 필요한 처리를 해보자.
화면이 동적으로 돌아가는 경우, 변경 시점을 알아내고 처리하기
화면의 회전 및 여러 configuration 사항의 변경은 Activity의 onConfigurationChanged()가 호출된다. 자세한 내용은 공식 가이드문서를 참조하기 바란다.
AndroidManifest.xml 수정
그럼 Activity에 해당 콜백 하나만 추가하면 되는걸까? 아니아니. 그냥은 불리지 않는다. 우선, AndroidManifest.xml의 해당 Activity에 다음과 같이 언제 onConfigurationChanged()콜백이 불릴지 명시해줘야 한다.
<activity
...
android:configChanges="orientation|screenSize|screenLayout"
>
configChanges 항목은 공식 가이드문서를 참고하자. 문서 내용을 인용하면 다음과 같은 항목들이 조합될 수 있다.
Value | Description |
---|---|
“density “ | The display density has changed — the user might have specified a different display scale, or a different display might now be active.Added in API level 24. |
“fontScale “ | The font scaling factor has changed — the user has selected a new global font size. |
“keyboard “ | The keyboard type has changed — for example, the user has plugged in an external keyboard. |
“keyboardHidden “ | The keyboard accessibility has changed — for example, the user has revealed the hardware keyboard. |
“layoutDirection “ | The layout direction has changed — for example, changing from left-to-right (LTR) to right-to-left (RTL).Added in API level 17. |
“locale “ | The locale has changed — the user has selected a new language that text should be displayed in. |
“mcc “ | The IMSI mobile country code (MCC) has changed — a SIM has been detected and updated the MCC. |
“mnc “ | The IMSI mobile network code (MNC) has changed — a SIM has been detected and updated the MNC. |
“navigation “ | The navigation type (trackball/dpad) has changed. (This should never normally happen.) |
“orientation “ | The screen orientation has changed — the user has rotated the device.Note: If your application targets Android 3.2 (API level 13) or higher, then you should also declare the "screenSize" and "screenLayout" configurations, because they might also change when a device switches between portrait and landscape orientations. |
“screenLayout “ | The screen layout has changed — a different display might now be active. |
“screenSize “ | The current available screen size has changed.This represents a change in the currently available size, relative to the current aspect ratio, so will change when the user switches between landscape and portrait.Added in API level 13. |
“smallestScreenSize “ | The physical screen size has changed.This represents a change in size regardless of orientation, so will only change when the actual physical screen size has changed such as switching to an external display. A change to this configuration corresponds to a change in the smallestWidth configuration.Added in API level 13. |
“touchscreen “ | The touchscreen has changed. (This should never normally happen.) |
“uiMode “ | The user interface mode has changed — the user has placed the device into a desk or car dock, or the night mode has changed. For more information about the different UI modes, see UiModeManager .Added in API level 8. |
표에서 빨간색 글자로 표시해놨는데, orientation을 우선 사용해야한다. 그리고 설명을 보면, Android 3.2(API 13)이상이면 screenSize와 screenLayout도 동시에 사용해야 된다고 나온다. 실제로 orientation만 사용하면 콜백이 호출되지 않는다.
onConfigurationChanged() 구현
자, 이제 onConfigurationChanged()는 불린다. 이 콜백안에서 필요한 처리를 하자. portrait/landscape의 경우에는 굳이 Activity.getResources()를 사용할 필요가 없다. 인자로 넘어오는 Configuration에 정보가 들어있다.
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
}
}
그럼, 4방향 회전상태는 어떻게될까? 아쉽게도 Configuration에 해당 정보는 없다. 앞에서와 동일하게, DisplayManager를 이용한다. 어쨌든 어느쪽으로든 화면의 회전이 발생하면 onConfigurationChanged()가 불리기 때문에, 이 안에서 체크를 하게되면 변경시, 변경사항을 알 수 있게된다.
val display = getSystemService<DisplayManager>()?.getDisplay(Display.DEFAULT_DISPLAY)
if (display != null) {
when(display.rotation) {
Surface.ROTATION_0 -> { }
Surface.ROTATION_90 -> { }
Surface.ROTATION_180 -> { }
Surface.ROTATION_270 -> { }
}
}
이렇게 화면 회전을 처리하는 방법을 알아봤다. 어렵다고 할 수는 없지만, 꽤나 번거롭고 까다로워 보이는건 사실. 뭐, 그래서 기록해 두려고 작성하는거니까. 여기까지.