펼친 상태의 대형 디스플레이와 고유한 접힌 상태는 폴더블 기기에서 새로운 사용자 환경을 제공합니다. 앱이 기기의 접힌 상태를 인식하도록 하려면 접힘 및 힌지와 같은 폴더블 기기 창 기능에 API 표시 영역을 제공하는 라이브러리인 Jetpack WindowManager 라이브러리를 사용합니다. 앱이 접힌 상태를 인식하는 경우 접힘 또는 힌지 영역에 중요한 콘텐츠를 배치하지 않고 접힘과 힌지를 자연스러운 구분선으로 사용하도록 레이아웃을 조정할 수 있습니다.
기기가 탁자 모드나 책 모드와 같은 구성을 지원하는지 여부를 파악하면 다양한 레이아웃을 지원하거나 특정 기능을 제공하는 것에 관한 결정을 내릴 수 있습니다.
창 정보
창을 노출하는 Jetpack WindowManager의 WindowInfoTracker
인터페이스
확인할 수 있습니다. 인터페이스의 windowLayoutInfo()
메서드는 앱에 폴더블 기기의 접힌 상태를 알려주는 WindowLayoutInfo
데이터 스트림을 반환합니다. WindowInfoTracker#getOrCreate()
메서드는 WindowInfoTracker
인스턴스를 만듭니다.
WindowManager는 Kotlin Flow 및 Java 콜백을 사용하여 WindowLayoutInfo
데이터를 수집하도록 지원합니다.
Kotlin 흐름
WindowLayoutInfo
데이터 수집을 시작하고 중지하려면 재시작 가능한
수명 주기 인식 코루틴 - repeatOnLifecycle
코드 블록이
수명 주기가 STARTED
이상이면 실행되고
수명 주기는 STOPPED
입니다. 코드 블록 실행이 자동으로 다시 시작됨
수명 주기가 다시 STARTED
일 때. 다음 예에서 코드 블록은 WindowLayoutInfo
데이터를 수집하고 사용합니다.
class DisplayFeaturesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDisplayFeaturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
.windowLayoutInfo(this@DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
}
}
}
Java 콜백
콜백 호환성 레이어는
androidx.window:window-java
종속 항목을 사용하면
Kotlin 흐름을 사용하지 않고 WindowLayoutInfo
업데이트 아티팩트에는 다음이 포함됩니다.
WindowInfoTrackerCallbackAdapter
클래스:
WindowInfoTracker
:
WindowLayoutInfo
업데이트를 수신합니다. 예를 들면 다음과 같습니다.
public class SplitLayoutActivity extends AppCompatActivity {
private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private ActivitySplitLayoutBinding binding;
private final LayoutStateChangeCallback layoutStateChangeCallback =
new LayoutStateChangeCallback();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
windowInfoTracker =
new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}
@Override
protected void onStart() {
super.onStart();
windowInfoTracker.addWindowLayoutInfoListener(
this, Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
super.onStop();
windowInfoTracker
.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
@Override
public void accept(WindowLayoutInfo newLayoutInfo) {
SplitLayoutActivity.this.runOnUiThread( () -> {
// Use newLayoutInfo to update the layout.
});
}
}
}
RxJava 지원
이미 RxJava
(버전 2
또는 3
)을 사용 중인 경우
아티팩트를 활용하여
Observable
또는 Flowable
Kotlin 흐름을 사용하지 않고 WindowLayoutInfo
업데이트를 수집할 수 있습니다.
androidx.window:window-rxjava2
및
androidx.window:window-rxjava3
종속 항목에는 다음이 포함됩니다.
WindowInfoTracker#windowLayoutInfoFlowable()
및
WindowInfoTracker#windowLayoutInfoObservable()
메서드를 사용하면
앱에서 WindowLayoutInfo
업데이트를 받을 수 있습니다. 예를 들면 다음과 같습니다.
class RxActivity: AppCompatActivity {
private lateinit var binding: ActivityRxBinding
private var disposable: Disposable? = null
private lateinit var observable: Observable<WindowLayoutInfo>
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Create a new observable.
observable = WindowInfoTracker.getOrCreate(this@RxActivity)
.windowLayoutInfoObservable(this@RxActivity)
}
@Override
protected void onStart() {
super.onStart();
// Subscribe to receive WindowLayoutInfo updates.
disposable?.dispose()
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
@Override
protected void onStop() {
super.onStop();
// Dispose of the WindowLayoutInfo observable.
disposable?.dispose()
}
}
폴더블 디스플레이 기능
Jetpack WindowManager의 WindowLayoutInfo
클래스는
DisplayFeature
요소 목록으로 사용할 수 있는 디스플레이 창입니다.
FoldingFeature
는 정보를 제공하는 DisplayFeature
유형입니다.
다음 내용을 포함하여 폴더블 디스플레이에 관해 자세히 알아보세요.
state
: 기기의 접힌 상태(FLAT
또는HALF_OPENED
)orientation
: 접힘 또는 힌지의 방향(HORIZONTAL
또는VERTICAL
)isSeparating
: 접힘 또는 힌지가 두 개의 논리 디스플레이 영역을 생성하는지 여부 true 또는 false
HALF_OPENED
인 폴더블 기기는 항상 isSeparating
를 true로 보고합니다.
화면이 두 개의 디스플레이 영역으로 분리되기 때문입니다. 또한 듀얼 화면 기기에서 애플리케이션이 두 화면에 걸쳐 있는 경우 isSeparating
은 항상 true입니다.
FoldingFeature
bounds
속성(DisplayFeature
에서 상속됨)은 접힘 또는 힌지와 같은 접기 기능의 경계 직사각형을 나타냅니다.
경계는 접기 기능을 기준으로 화면에 요소를 배치하는 데 사용할 수 있습니다.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collects from WindowInfoTracker when the lifecycle is // STARTED and stops collection when the lifecycle is STOPPED. WindowInfoTracker.getOrCreate(this@MainActivity) .windowLayoutInfo(this@MainActivity) .collect { layoutInfo -> // New posture information. val foldingFeature = layoutInfo.displayFeatures .filterIsInstance<FoldingFeature>() .firstOrNull() // Use information from the foldingFeature object. } } } }
자바
private WindowInfoTrackerCallbackAdapter windowInfoTracker; private final LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... windowInfoTracker = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this)); } @Override protected void onStart() { super.onStart(); windowInfoTracker.addWindowLayoutInfoListener( this, Runnable::run, layoutStateChangeCallback); } @Override protected void onStop() { super.onStop(); windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback); } class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> { @Override public void accept(WindowLayoutInfo newLayoutInfo) { // Use newLayoutInfo to update the Layout. List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures(); for (DisplayFeature feature : displayFeatures) { if (feature instanceof FoldingFeature) { // Use information from the feature object. } } } }
탁자 상태
앱은 FoldingFeature
객체에 포함된 정보를 사용하여 다음을 수행할 수 있습니다.
탁자, 휴대전화가 표면에 놓여 있는 상태, 힌지가
가로 위치에 있고 폴더블 화면이 반만 열려 있습니다.
탁자 모드는 사용자가 휴대전화를 손에 쥐고 있지 않은 상태에서 휴대전화를 조작할 수 있는 편리함을 제공합니다. 탁자 모드는 미디어를 보고, 사진을 찍고, 영상 통화를 할 때 적합합니다.

FoldingFeature.State
및 FoldingFeature.Orientation
를 사용하여 다음을 결정합니다.
기기가 탁자 상태인지 여부:
Kotlin
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
자바
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }
기기가 탁자 모드임을 확인했으면 앱 레이아웃을 업데이트합니다. 변경할 수 있습니다 미디어 앱의 경우 일반적으로 재생 위치를 바로 아래에 보조 콘텐츠를 추가하여 핸즈프리 보기 또는 청취 환경입니다.
Android 15 (API 수준 35) 이상에서는 동기 API를 호출하여 현재 기기에 관계없이 기기가 탁자 상태를 지원하는지 감지 정의합니다.
이 API는 기기에서 지원하는 자세 목록을 제공합니다. 목록이 탁자 상태가 포함된 경우 앱 레이아웃을 분할하여 상태를 지원할 수 있습니다. 앱 UI에서 탁자 및 전체 화면 레이아웃의 A/B 테스트를 실행할 수 있습니다.
Kotlin
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(TABLE_TOP)) { // Device supports tabletop posture. } }
자바
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures(); if (postures.contains(SupportedPosture.TABLETOP)) { // Device supports tabletop posture. } }
예
MediaPlayerActivity
앱: Media3 사용 방법 보기 접기 인식 동영상을 만들기 위한 Exoplayer 및 WindowManager 있습니다.Jetpack WindowManager로 폴더블 기기에서 카메라 앱 최적화하기 Codelab: 사진 앱에 탁자 모드를 구현하는 방법을 알아봅니다. 프로그램 화면 상단 절반 (스크롤 없이 볼 수 있는 부분)에 있는 뷰파인더와 컨트롤을 배치해야 합니다.
도서 상태
또 다른 고유한 폴더블 기능은 기기가 반쯤 열려 있고 힌지가 수직인 책 모드입니다. 책 모드는 eBook을 읽을 때 유용합니다. 다음으로 바꿉니다. 제본된 책처럼 열리는 대형 화면 폴더블의 두 페이지 레이아웃, 책 자세는 실제 책을 읽는 경험을 캡처합니다.
다른 측면을 포착하려는 경우 사진에도 사용할 수 있습니다. 핸즈프리 사진을 찍을 때의 비율을 나타냅니다.
탁자 모드에 사용된 것과 동일한 기법으로 책 모드를 구현합니다. 이 코드에서 접기 기능 방향이 가로가 아닌 세로로:
Kotlin
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL }
자바
boolean isBookPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL); }
창 크기 변경
기기 구성이 변경되면(예: 기기가 접히거나 펼쳐지는 경우, 회전되는 경우, 멀티 윈도우 모드에서 창 크기가 조절되는 경우) 앱의 디스플레이 영역이 변경될 수 있습니다.
Jetpack WindowManager의 WindowMetricsCalculator
클래스를 사용하면 다음 작업을 할 수 있습니다.
현재 및 최대 창 측정항목을 검색합니다. WindowManager
WindowMetrics
는 API 수준 30에서 도입된 플랫폼 WindowMetrics
와 마찬가지로 창 경계를 제공하지만 API는 API 수준 14까지 하위 호환됩니다.
창 크기 클래스 사용을 참고하세요.
추가 리소스
샘플
- Jetpack WindowManager: Jetpack 사용 방법의 예 WindowManager 라이브러리
- Jetcaster : Compose를 사용한 탁자 모드 구현