개발자는 폴더블용 애플리케이션을 만들 때 특히 가로 형식으로 열리는 (rotation_0 = landscape) 삼성 트라이폴드나 원래 Pixel Fold와 같은 기기의 경우 고유한 어려움에 직면하는 경우가 많습니다. 개발자 실수는 다음과 같습니다.
- 기기 방향에 대한 잘못된 가정
- 간과된 사용 사례
- 구성 변경 시 값을 다시 계산하거나 캐시하지 못함
구체적인 기기 관련 문제는 다음과 같습니다.
- 커버와 내부 디스플레이 간 기기 자연스러운 방향 불일치(rotation_0 = 세로 모드에 기반한 가정)로 인해 접기 및 펼치기 여정에서 앱이 실패함
- 다양한 화면 밀도 및 잘못된 밀도 구성 변경 처리
- 자연스러운 방향에 대한 카메라 센서 종속성으로 인해 발생하는 카메라 미리보기 문제
폴더블 기기에서 고품질 사용자 환경을 제공하려면 다음의 중요한 영역에 집중하세요.
- 기기의 실제 방향이 아닌 앱이 차지하는 실제 화면 영역을 기반으로 앱의 방향을 결정합니다.
- 카메라 미리보기를 업데이트하여 기기 방향과 가로세로 비율을 올바르게 관리하고, 옆으로 표시되는 미리보기를 방지하고, 이미지가 늘어나거나 잘리는 것을 방지합니다.
ViewModel또는 유사한 접근 방식으로 상태를 유지하거나 화면 밀도 변경 및 방향 변경을 수동으로 처리하여 기기 접기 또는 펼치기 중에 앱 연속성을 유지합니다. 이렇게 하면 앱이 다시 시작되거나 상태가 손실되지 않습니다.- 동작 센서를 활용하는 앱의 경우 화면의 현재 방향에 맞게 좌표계를 조정하고 rotation_0 = 세로 모드를 기반으로 한 가정을 피하여 정확한 사용자 상호작용을 보장합니다.
적응형 빌드
앱이 이미 적응형이고 대형 화면 앱 품질 가이드라인에 설명된 최적화 수준 (2단계)을 준수하는 경우 앱은 폴더블 기기에서 잘 작동해야 합니다. 그렇지 않은 경우 트라이폴드 및 가로 모드 폴더블의 구체적인 세부정보를 다시 확인하기 전에 다음 기본 Android 적응형 개발 개념을 검토하세요.
적응형 레이아웃
UI는 다양한 화면 크기뿐만 아니라 펼치기, 멀티 윈도우 또는 데스크톱 창 모드 진입과 같은 실시간 가로세로 비율 변경도 처리해야 합니다. 다음 작업을 실행하는 방법에 관한 자세한 안내는 적응형 레이아웃 정보를 참고하세요.
- 적응형 레이아웃 설계 및 구현
- 창 크기에 따라 앱의 기본 탐색 조정
- 창 크기 클래스를 사용하여 앱의 UI 적응
- Jetpack API를 사용하여 목록‑세부정보와 같은 표준 레이아웃의 구현 간소화
창 크기 클래스
가로 모드 폴더블 및 삼중 폴더블을 비롯한 폴더블 기기는 소형, 보통, 펼침 창 크기 클래스 간에 즉시 전환할 수 있습니다. 이러한 클래스를 이해하고 구현하면 앱이 현재 기기 상태에 맞는 올바른 탐색 구성요소와 콘텐츠 밀도를 표시할 수 있습니다.
다음 예에서는 Material 3 적응형 라이브러리를 사용하여 먼저 currentWindowAdaptiveInfo() 함수를 호출한 다음 세 가지 창 크기 클래스에 해당하는 레이아웃을 사용하여 앱에 사용 가능한 공간을 확인합니다.
val adaptiveInfo = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)
val windowSizeClass = adaptiveInfo.windowSizeClass
when {
windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> // Large
windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) -> // Medium
else -> // Compact
}
자세한 내용은 창 크기 클래스 사용을 참고하세요.
대형 화면 앱 품질
대형 화면 앱 품질 가이드라인의 Tier 2 (대형 화면 최적화) 또는 Tier 1 (대형 화면 차별화)을 준수하면 앱이 트라이폴드 기기, 가로 모드 폴더블, 기타 대형 화면 기기에서 매력적인 사용자 환경을 제공할 수 있습니다. 가이드라인에서는 적응형에서 차별화된 환경으로 전환하기 위해 여러 등급 수준에서 중요한 검사를 다룹니다.
Android 16 이상
Android 16 (API 수준 36) 이상을 타겟팅하는 앱의 경우 시스템은 최소 너비가 600dp 이상인 디스플레이에서 방향, 크기 조절 가능 여부, 가로세로 비율 제한을 무시합니다. 앱이 가로세로 비율이나 사용자가 선호하는 방향에 관계없이 전체 디스플레이 창을 채우고 레터박스 처리 호환성 모드는 더 이상 사용되지 않습니다.
특별 고려사항
트리폴드와 가로 모드 폴더블은 특히 센서, 카메라 미리보기, 구성 연속성 (접거나 펼치거나 크기를 조절할 때 상태 유지)과 관련하여 특정 처리가 필요한 고유한 하드웨어 동작을 도입합니다.
카메라 미리보기
가로 모드 폴더블 또는 가로세로 비율 계산 (멀티 윈도우, 데스크톱 창, 연결된 디스플레이와 같은 시나리오)에서 흔히 발생하는 문제는 카메라 미리보기가 늘어나거나, 옆으로 표시되거나, 잘리거나, 회전되는 경우입니다.
가정 불일치
이 문제는 앱이 가로세로 비율, 센서 방향과 같은 카메라 기능과 기기 방향, 자연스러운 방향과 같은 기기 기능 간의 고정된 관계를 가정할 수 있기 때문에 대형 화면 및 폴더블 기기에서 자주 발생합니다.
새로운 폼 팩터는 이러한 가정에 도전합니다. 폴더블 기기는 기기 회전이 변경되지 않아도 디스플레이 크기와 가로세로 비율을 변경할 수 있습니다. 예를 들어 기기를 펼치면 가로세로 비율이 변경되지만 사용자가 기기를 회전하지 않으면 회전은 동일하게 유지됩니다. 앱이 가로세로 비율이 기기 회전과 관련이 있다고 가정하면 카메라 미리보기가 잘못 회전하거나 확장될 수 있습니다. 앱이 카메라 센서 방향이 세로 모드 기기 방향과 일치한다고 가정하는 경우에도 동일한 문제가 발생할 수 있습니다. 이는 가로 모드 폴더블에서는 항상 사실이 아닙니다.
해결 방법 1: Jetpack CameraX (가장 좋음)
가장 간단하고 강력한 솔루션은 Jetpack CameraX 라이브러리를 사용하는 것입니다. PreviewView UI 요소는 모든 미리보기 복잡성을 자동으로 처리하도록 설계되었습니다.
PreviewView는 센서 방향, 기기 회전, 크기 조정을 올바르게 조정합니다.- 일반적으로 중앙에 배치하고 잘라내어 (FILL_CENTER) 카메라 이미지의 가로세로 비율을 유지합니다.
- 필요한 경우 확장 유형을
FIT_CENTER로 설정하여 미리보기를 레터박스 처리할 수 있습니다.
자세한 내용은 CameraX 문서의 미리보기 구현을 참고하세요.
해결 방법 2: CameraViewfinder
기존 Camera2 코드베이스를 사용하는 경우 CameraViewfinder 라이브러리(API 수준 21과 하위 호환됨)가 또 다른 최신 솔루션입니다. TextureView 또는 SurfaceView를 사용하고 필요한 모든 변환 (가로세로 비율, 크기, 회전)을 적용하여 카메라 피드 표시를 간소화합니다.
자세한 내용은 카메라 뷰파인더 소개 블로그 게시물 및 카메라 미리보기 개발자 가이드를 참고하세요.
해결 방법 3: 수동 Camera2 구현
CameraX 또는 CameraViewfinder를 사용할 수 없는 경우 방향과 가로세로 비율을 수동으로 계산하고 각 구성 변경 시 계산이 업데이트되도록 해야 합니다.
CameraCharacteristics에서 카메라 센서 방향 (예: 0, 90, 180, 270도)을 가져옵니다.- 기기의 현재 디스플레이 회전 (예: 0, 90, 180, 270도)을 가져옵니다.
- 이 두 값을 사용하여
SurfaceView또는TextureView에 필요한 변환을 결정합니다. - 왜곡을 방지하려면 출력
Surface의 가로세로 비율이 카메라 미리보기의 가로세로 비율과 일치해야 합니다. - 카메라 앱이 멀티 윈도우 또는 데스크톱 창 모드에서 또는 연결된 디스플레이에서 화면의 일부로 실행될 수 있습니다. 따라서 화면 크기를 사용하여 카메라 뷰파인더의 크기를 결정해서는 안 되며 대신 창 측정항목을 사용해야 합니다.
자세한 내용은 카메라 미리보기 개발자 가이드 및 다양한 폼 팩터의 카메라 앱 동영상을 참고하세요.
해결 방법 4: 인텐트를 사용하여 기본 카메라 작업 실행
카메라 기능이 많이 필요하지 않다면 기기의 기본 카메라 애플리케이션을 사용하여 사진이나 동영상 촬영과 같은 기본적인 카메라 작업을 실행하는 것이 간단하고 직접적인 솔루션입니다. 카메라 라이브러리와 통합할 필요가 없습니다. 대신 인텐트를 사용하세요.
자세한 내용은 카메라 인텐트를 참고하세요.
구성 및 연속성
폴더블 기기는 UI 다재다능성을 향상하지만 폴더블이 아닌 기기보다 더 많은 구성 변경사항을 시작할 수 있습니다. 앱은 앱 상태를 유지하거나 복원하면서 기기 회전, 접기/펼치기, 멀티 윈도우 또는 데스크톱 모드에서의 창 크기 조절과 같은 이러한 구성 변경사항과 그 조합을 관리해야 합니다. 예를 들어 앱은 다음 연속성을 유지해야 합니다.
- 비정상 종료되거나 사용자에게 방해가 되는 변경사항 (예: 화면을 전환하거나 앱을 백그라운드로 전송할 때) 없이 앱 상태를 유지합니다.
- 스크롤 가능한 필드의 스크롤 위치
- 텍스트 필드에 입력한 텍스트 및 키보드 상태
- 구성 변경이 시작되었을 때 중단된 지점부터 재생이 다시 시작되도록 하는 미디어 재생 위치
자주 트리거되는 구성 변경사항에는 screenSize, smallestScreenSize, screenLayout, orientation, density, fontScale, touchscreen, keyboard이 포함됩니다.
android:configChanges 및 구성 변경 처리를 참고하세요. 앱 상태 관리에 관한 자세한 내용은 UI 상태 저장을 참고하세요.
밀도 구성 변경
트리폴드 및 가로 모드 폴더블 기기의 외부 및 내부 화면은 픽셀 밀도가 다를 수 있습니다. 따라서 density의 구성 변경사항을 관리하려면 추가적인 주의가 필요합니다. Android는 일반적으로 디스플레이 밀도가 변경되면 활동을 다시 시작하므로 데이터가 손실될 수 있습니다. 시스템이 활동을 다시 시작하지 못하도록 하려면 매니페스트에서 밀도 처리를 선언하고 앱에서 프로그래매틱 방식으로 구성 변경을 관리하세요.
AndroidManifest.xml 구성
density: 앱이 화면 밀도 변경을 처리한다고 선언합니다.- 기타 구성 변경사항:
screenSize,orientation,keyboardHidden,fontScale등 자주 발생하는 기타 구성 변경사항도 선언하는 것이 좋습니다.
밀도 (및 기타 구성 변경)를 선언하면 시스템이 활동을 다시 시작하지 않고 onConfigurationChanged()를 호출합니다.
onConfigurationChanged() 구현
밀도 변경이 발생하면 콜백에서 리소스를 업데이트해야 합니다 (예: 비트맵 다시 로드 또는 레이아웃 크기 다시 계산).
- DPI가
newConfig.densityDpi로 변경되었는지 확인합니다. - 맞춤 뷰, 맞춤 드로어블 등을 새 밀도로 재설정
처리할 리소스 항목
- 이미지 리소스: 비트맵과 드로어블을 밀도별 리소스로 바꾸거나 크기를 직접 조정합니다.
- 레이아웃 단위 (dp에서 px로 변환): 뷰 크기, 여백, 패딩 재계산
- 글꼴 및 텍스트 크기: sp 단위 텍스트 크기 다시 적용
- 맞춤
View/Canvas그리기:Canvas를 그리는 데 사용되는 픽셀 기반 값을 업데이트합니다.
앱 방향 결정
적응형을 빌드할 때는 실제 기기 회전에 의존하면 안 됩니다. 대형 화면 기기에서 무시되고 멀티 윈도우 모드의 앱은 기기와 방향이 다를 수 있기 때문입니다. 대신 Configuration.orientation 또는 WindowMetrics를 사용하여 앱이 현재 창 크기에 따라 가로 또는 세로 방향인지 확인하세요.
해결 방법 1: Configuration.orientation 사용
이 속성은 앱이 현재 표시되는 방향을 식별합니다.
해결 방법 2: WindowMetrics#getBounds() 사용
앱의 현재 디스플레이 경계를 가져오고 너비와 높이를 확인하여 방향을 확인할 수 있습니다.
휴대전화 (또는 폴더블의 외부 화면)에서는 앱 방향을 제한해야 하지만 대형 화면 기기에서는 제한하지 않아야 하는 경우 휴대전화에서 앱 방향 제한을 참고하세요.
자세 및 디스플레이 모드
탁자 모드, HALF_OPENED과 같은 폴더블 모드와 상태는 세로형 폴더블과 가로형 폴더블 모두에서 지원됩니다. 하지만 3단 접기는 테이블 모드를 지원하지 않으며 HALF_OPENED를 사용할 수 없습니다. 반면 트라이폴드는 완전히 펼쳤을 때 고유한 사용자 환경을 위한 더 큰 화면을 제공합니다.
HALF_OPENED를 지원하는 폴더블에서 앱을 차별화하려면 FoldingFeature와 같은 Jetpack WindowManager API를 사용하세요.
다음 개발자 가이드에서 폴더블 자세, 상태, 카메라 미리보기 지원에 관해 자세히 알아보세요.
폴더블 기기는 독특한 시청 환경을 제공합니다. 후면 디스플레이 모드와 듀얼 화면 모드를 사용하면 후면 카메라 셀카 미리보기, 내부 및 외부 화면의 다르지만 동시에 보이는 디스플레이 등 폴더블 기기의 특별한 디스플레이 기능을 빌드할 수 있습니다. 자세한 내용은 다음을 참고하세요.
방향을 자연 센서 방향으로 잠금
매우 구체적인 사용 사례(특히 기기의 접힌 상태와 관련이 없는 전체 화면을 차지해야 하는 앱)의 경우 nosensor 플래그를 사용하면 앱을 기기의 자연스러운 방향으로 잠글 수 있습니다. 예를 들어 Pixel Fold에서 접었을 때 기기의 자연스러운 방향은 세로 모드이고 펼쳤을 때 자연스러운 방향은 가로 모드입니다. nosensor 플래그를 추가하면 외부 디스플레이에서 실행할 때는 앱이 세로 모드로 강제 고정되고 내부 디스플레이에서 실행할 때는 가로 모드로 강제 고정됩니다.
<activity
android:name=".MainActivity"
android:screenOrientation="nosensor">
게임 및 XR 센서 리매핑
게임과 XR 앱의 경우 원시 센서 데이터 (예: 자이로스코프 또는 가속도계)가 기기 고정 좌표계에 제공됩니다. 사용자가 기기를 회전하여 게임을 가로 모드로 플레이하면 센서 축이 화면과 함께 회전하지 않아 게임 컨트롤이 잘못됩니다.
이 문제를 수정하려면 현재 Display.getRotation()을 확인하고 이에 따라 축을 다시 매핑하세요.
- 회전 0: x=x, y=y
- 90도 회전: x=-y, y=x
- 회전 180: x=-x, y=-y
- 회전 270: x=y, y=-x
회전 벡터 (나침반 또는 XR 앱에 사용)의 경우 SensorManager.remapCoordinateSystem()을 사용하여 현재 회전에 따라 카메라 렌즈 방향 또는 화면 상단을 새 축에 매핑합니다.
앱 호환성
애플리케이션은 모든 폼 팩터와 연결된 디스플레이 간의 호환성을 보장하기 위해 앱 품질 가이드라인을 따라야 합니다. 애플리케이션이 가이드라인을 준수할 수 없는 경우 기기 제조업체는 호환성 처리를 구현할 수 있지만 사용자 환경이 저하될 수 있습니다.
자세한 내용은 플랫폼에 제공된 호환성 해결 방법의 전체 목록을 검토하세요. 특히 앱 동작을 변경할 수 있는 카메라 미리보기, 재정의, Android 16 API 변경사항과 관련된 해결 방법을 검토하세요.
적응형 앱 빌드에 관해 자세히 알아보려면 대형 화면 앱 품질을 참고하세요.