폴더블 기기는 독특한 시청 환경을 제공합니다. 후면 디스플레이 모드와 듀얼 화면 모드를 사용하면 후면 카메라 셀카 미리보기, 내부 및 외부 화면의 다르지만 동시에 보이는 디스플레이 등 폴더블 기기의 특별한 디스플레이 기능을 빌드할 수 있습니다.
후면 디스플레이 모드
일반적으로 폴더블 기기를 펼치면 내부 화면만 활성화됩니다. 후면 디스플레이 모드를 사용하면 활동을 폴더블 기기의 외부 화면으로 이동할 수 있습니다. 이 화면은 일반적으로 기기가 펼쳐져 있는 동안 사용자의 반대쪽을 향합니다. 내부 디스플레이는 자동으로 꺼집니다.
이제는 외부 화면에 카메라 미리보기를 표시할 수도 있습니다. 이를 통해 사용자는 후면 카메라로 셀카를 찍을 수 있으며, 사진 촬영 성능이 훨씬 낫습니다.
후면 디스플레이 모드를 활성화하려면 사용자는 앱이 화면을 전환하도록 허용하는 대화상자에 응답해야 합니다. 예를 들면 다음과 같습니다.
시스템에서 대화상자를 생성하므로 별도로 개발할 필요가 없습니다. 기기 상태에 따라 다른 대화상자가 표시됩니다. 예를 들어 기기가 닫혀 있으면 시스템에서 사용자에게 기기를 펼치도록 안내합니다. 대화상자를 맞춤설정할 수 없으며 OEM의 기기에 따라 다를 수 있습니다.
Pixel Fold 카메라 앱으로 후면 디스플레이 모드를 사용해 볼 수 있습니다. Jetpack WindowManager로 폴더블 기기에서 카메라 앱 최적화 Codelab의 샘플 구현을 참고하세요.
듀얼 화면 모드
듀얼 화면 모드를 사용하면 폴더블의 두 디스플레이에 콘텐츠를 동시에 표시할 수 있습니다. 듀얼 화면 모드는 Android 14(API 수준 34) 이상을 실행하는 Pixel Fold에서 사용할 수 있습니다.
듀얼 화면 인터프리터를 사용 사례로 들 수 있습니다.
프로그래매틱 방식으로 모드 사용 설정
라이브러리 버전 1.2.0-beta03부터 Jetpack WindowManager API를 통해 후면 디스플레이 모드와 듀얼 화면 모드에 액세스할 수 있습니다.
앱의 모듈 build.gradle
파일에 WindowManager 종속 항목을 추가합니다.
Groovy
dependencies { implementation "androidx.window:window:1.2.0-beta03" }
Kotlin
dependencies { implementation("androidx.window:window:1.2.0-beta03") }
진입점은 WindowAreaController
로, 디스플레이 간 또는 기기의 디스플레이 영역 간 창 이동과 관련된 정보와 동작을 제공합니다. WindowAreaController
를 사용하면 사용 가능한 WindowAreaInfo
객체의 목록을 쿼리할 수 있습니다.
WindowAreaInfo
를 사용하여 활성 창 영역 기능을 나타내는 인터페이스인 WindowAreaSession
에 액세스합니다. WindowAreaSession
를 사용하여 특정 WindowAreaCapability
의 사용 가능 여부를 확인합니다.
각 기능은 특정 WindowAreaCapability.Operation
과 관련되어 있습니다.
버전 1.2.0-beta03에서 Jetpack WindowManager는 두 가지 작업을 지원합니다.
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
: 듀얼 화면 모드를 시작하는 데 사용됩니다.WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
: 후면 디스플레이 모드를 시작하는 데 사용됩니다.
다음은 앱의 기본 활동에서 후면 디스플레이 모드와 듀얼 화면 모드의 변수를 선언하는 방법을 보여주는 예입니다.
Kotlin
private lateinit var windowAreaController: WindowAreaController private lateinit var displayExecutor: Executor private var windowAreaSession: WindowAreaSession? = null private var windowAreaInfo: WindowAreaInfo? = null private var capabilityStatus: WindowAreaCapability.Status = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED private val dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
자바
private WindowAreaControllerCallbackAdapter windowAreaController = null; private Executor displayExecutor = null; private WindowAreaSessionPresenter windowAreaSession = null; private WindowAreaInfo windowAreaInfo = null; private WindowAreaCapability.Status capabilityStatus = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED; private WindowAreaCapability.Operation dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA; private WindowAreaCapability.Operation rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA;
다음은 활동의 onCreate()
메서드에서 변수를 초기화하는 방법입니다.
Kotlin
displayExecutor = ContextCompat.getMainExecutor(this) windowAreaController = WindowAreaController.getOrCreate() lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowAreaController.windowAreaInfos .map { info -> info.firstOrNull { it.type == WindowAreaInfo.Type.TYPE_REAR_FACING } } .onEach { info -> windowAreaInfo = info } .map { it?.getCapability(operation)?.status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED } .distinctUntilChanged() .collect { capabilityStatus = it } } }
자바
displayExecutor = ContextCompat.getMainExecutor(this); windowAreaController = new WindowAreaControllerCallbackAdapter(WindowAreaController.getOrCreate()); windowAreaController.addWindowAreaInfoListListener(displayExecutor, this); windowAreaController.addWindowAreaInfoListListener(displayExecutor, windowAreaInfos -> { for(WindowAreaInfo newInfo : windowAreaInfos){ if(newInfo.getType().equals(WindowAreaInfo.Type.TYPE_REAR_FACING)){ windowAreaInfo = newInfo; capabilityStatus = newInfo.getCapability(presentOperation).getStatus(); break; } } });
작업을 시작하기 전에 특정 기능을 사용할 수 있는지 확인합니다.
Kotlin
when (capabilityStatus) { WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> { // The selected display mode is not supported on this device. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> { // The selected display mode is not available. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> { // The selected display mode is available and can be enabled. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> { // The selected display mode is already active. } else -> { // The selected display mode status is unknown. } }
자바
if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED)) { // The selected display mode is not supported on this device. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE)) { // The selected display mode is not available. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) { // The selected display mode is available and can be enabled. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE)) { // The selected display mode is already active. } else { // The selected display mode status is unknown. }
듀얼 화면 모드
다음 예에서는 기능이 이미 활성 상태이면 세션을 닫고 그렇지 않으면 presentContentOnWindowArea()
함수를 호출합니다.
Kotlin
fun toggleDualScreenMode() { if (windowAreaSession != null) { windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.presentContentOnWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaPresentationSessionCallback = this ) } } }
자바
private void toggleDualScreenMode() { if(windowAreaSession != null) { windowAreaSession.close(); } else { Binder token = windowAreaInfo.getToken(); windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this); } }
앱의 기본 활동이 WindowAreaPresentationSessionCallback
인수로 사용된 것을 볼 수 있습니다.
API는 리스너 접근 방식을 사용합니다. 폴더블의 다른 디스플레이에 콘텐츠를 표시하도록 요청하면 리스너의 onSessionStarted()
메서드를 통해 반환되는 세션이 시작됩니다. 세션을 닫으면 onSessionEnded()
메서드에 확인 메시지가 표시됩니다.
리스너를 만들려면 다음과 같이 WindowAreaPresentationSessionCallback
인터페이스를 구현하세요.
Kotlin
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
자바
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
리스너는 onSessionStarted()
, onSessionEnded(),
, onContainerVisibilityChanged()
메서드를 구현해야 합니다. 콜백 메서드는 세션 상태를 알려주므로 적절하게 앱을 업데이트할 수 있습니다.
onSessionStarted()
콜백은 WindowAreaSessionPresenter
를 인수로 수신합니다. 이 인수는 창 영역에 액세스하고 콘텐츠를 표시할 수 있는 컨테이너입니다. 프레젠테이션은 사용자가 기본 애플리케이션 창을 닫으면 시스템에서 자동으로 닫을 수 있습니다. 또는 WindowAreaSessionPresenter#close()
를 호출하여 프레젠테이션을 닫을 수 있습니다.
다른 콜백의 경우 편의를 위해 함수 본문에서 오류를 확인하고 상태를 로깅합니다.
Kotlin
override fun onSessionStarted(session: WindowAreaSessionPresenter) { windowAreaSession = session val view = TextView(session.context) view.text = "Hello world!" session.setContentView(view) } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } } override fun onContainerVisibilityChanged(isVisible: Boolean) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = $isVisible") }
자바
@Override public void onSessionStarted(@NonNull WindowAreaSessionPresenter session) { windowAreaSession = session; TextView view = new TextView(session.getContext()); view.setText("Hello world, from the other screen!"); session.setContentView(view); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } } @Override public void onContainerVisibilityChanged(boolean isVisible) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = " + isVisible); }
생태계 전체에서 일관성을 유지하려면 Dual Screen 공식 아이콘을 사용하여 사용자에게 듀얼 화면 모드를 사용 설정하거나 사용 중지하는 방법을 알려주세요.
실제로 작동하는 샘플은 DualScreenActivity.kt를 참고하세요.
후면 디스플레이 모드
듀얼 화면 모드 예와 마찬가지로 다음 toggleRearDisplayMode()
함수 예는 기능이 이미 활성 상태이면 세션을 닫고 그렇지 않으면 transferActivityToWindowArea()
함수를 호출합니다.
Kotlin
fun toggleRearDisplayMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo?.getActiveSession( operation ) } windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.transferActivityToWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaSessionCallback = this ) } } }
자바
void toggleDualScreenMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo.getActiveSession( operation ) } windowAreaSession.close() } else { Binder token = windowAreaInfo.getToken(); windowAreaController.transferActivityToWindowArea(token, this, displayExecutor, this); } }
이 경우 표시된 활동은 WindowAreaSessionCallback
으로 사용되며 이는 콜백이 창 영역에 콘텐츠 표시를 허용하는 프레젠터를 수신하지 않고 대신 전체 활동을 다른 영역으로 전송하기 때문에 구현이 더 간단합니다.
Kotlin
override fun onSessionStarted() { Log.d(logTag, "onSessionStarted") } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } }
자바
@Override public void onSessionStarted(){ Log.d(logTag, "onSessionStarted"); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } }
생태계 전체에서 일관성을 유지하려면 후면 카메라 공식 아이콘을 사용하여 사용자에게 후면 디스플레이 모드를 사용 설정하거나 사용 중지하는 방법을 알려주세요.
추가 리소스
- Jetpack WindowManager로 폴더블 기기에서 카메라 앱 최적화 Codelab
androidx.window.area
패키지 요약- Jetpack WindowManager 샘플 코드: